OSCR

Quantitative Analysis of Cell and Tissue Shape During Mouse Cranial Neural Tube Closure.

Code ↔ Paper

1 match between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 1 match
  1. [1] § Software and datasets ↔ SeedWaterSegmenter/SeedWaterSegmenter.py, lines 1–22 · score 0.54 · SeedWater, David, Hutson, Mashburn, segmentation

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Python · 4,446 lines · 165 KB · other · 1 match

  1. #!/usr/bin/env python
  2. """SeedWater Segmenter is a watershed segmentation / registration
  3. program for image stacks developed at Vanderbilt University using
  4. Mahotas, WxPython, Matplotlib, Numpy, Scipy, and PIL.
  5. SeedWater enables users to generate seeds automatically and then edit
  6. them by hand in Matplotlib using custom interactions. Seeds for
  7. subsequent frames are generated as centroids of regions in the previous
  8. frame.
  9. Also, GifTiffLoader is a wrapper to automatically load Tiff and Gif
  10. files as numpy arrays using PIL.
  11. GifTiffLoader also relies on FilenameSort.
  12. Sponsored by the NSF and HFSP through the Shane Hutson Laboratory, part of
  13. Vanderbilt Institute of Integrative Biosystems Research (VIIBRE)."""
  14. from __future__ import absolute_import
  15. from __future__ import print_function
  16. __author__ = "David N. Mashburn <[email hidden]>"
  17. import os, sys
  18. # Replace the default buggy print buffers when using pythonw.exe:
  19. # See these to understand the issue we're fixing:
  20. # http://bugs.python.org/issue973507
  21. # http://bytes.com/topic/python/answers/727288-pythonw-exe-exits-prematurely
  22. if os.path.split(sys.executable)[1] == "pythonw.exe":
  23. outfile = open(os.devnull, "w")
  24. # alternative if you want to print output to the desktop instead
  25. # outfile = open('C:\\Users\\Test\\Desktop\\out.txt','w')
  26. sys.stdout = outfile
  27. sys.stderr = outfile
  28. from copy import copy, deepcopy
  29. from time import time, sleep
  30. import imp
  31. import numpy as np
  32. from numpy import nonzero
  33. from numpy.random import rand
  34. import scipy.ndimage
  35. import scipy.sparse
  36. import GifTiffLoader as GTL
  37. from GifTiffLoader import Image # either pillow or PIL
  38. import wx
  39. import math
  40. from scipy.ndimage import center_of_mass, gaussian_filter, median_filter
  41. import matplotlib
  42. #matplotlib.use("WxAgg")
  43. # from matplotlib.nxutils import points_inside_poly # deprecated, work-around function in mpl_polygon_lasso
  44. from .mpl_polygon_lasso import PolyLasso, points_inside_poly
  45. import matplotlib.pyplot as plt
  46. import mahotas
  47. import ImageContour
  48. import EllipseFitter
  49. from . import ExcelHelper
  50. from . import SWHelpers
  51. class Timer(object):
  52. def __init__(self):
  53. self.timeStart = time()
  54. def test(self):
  55. print(time() - self.timeStart)
  56. self.timeStart = time()
  57. def reset(self):
  58. self.timeStart = time()
  59. timer = Timer()
  60. try:
  61. import cv
  62. HAS_CV = True
  63. def cv2array(im):
  64. depth2dtype = {
  65. cv.IPL_DEPTH_8U: "uint8",
  66. cv.IPL_DEPTH_8S: "int8",
  67. cv.IPL_DEPTH_16U: "uint16",
  68. cv.IPL_DEPTH_16S: "int16",
  69. cv.IPL_DEPTH_32S: "int32",
  70. cv.IPL_DEPTH_32F: "float32",
  71. cv.IPL_DEPTH_64F: "float64",
  72. }
  73. arrdtype = im.depth
  74. a = np.fromstring(
  75. im.tostring(),
  76. dtype=depth2dtype[im.depth],
  77. count=im.width * im.height * im.nChannels,
  78. )
  79. a.shape = (im.height, im.width, im.nChannels)
  80. return a
  81. def array2cv(a):
  82. dtype2depth = {
  83. "uint8": cv.IPL_DEPTH_8U,
  84. "int8": cv.IPL_DEPTH_8S,
  85. "uint16": cv.IPL_DEPTH_16U,
  86. "int16": cv.IPL_DEPTH_16S,
  87. "int32": cv.IPL_DEPTH_32S,
  88. "float32": cv.IPL_DEPTH_32F,
  89. "float64": cv.IPL_DEPTH_64F,
  90. }
  91. try:
  92. nChannels = a.shape[2]
  93. except:
  94. nChannels = 1
  95. cv_im = cv.CreateImageHeader(
  96. (a.shape[1], a.shape[0]), dtype2depth[str(a.dtype)], nChannels
  97. )
  98. cv.SetData(cv_im, a.tostring(), a.dtype.itemsize * nChannels * a.shape[1])
  99. return cv_im
  100. def cvWater(a, m):
  101. im = cv.fromarray(np.array(a, dtype=np.uint8))
  102. imC = cv.CreateImage(cv.GetSize(im), 8, 3) # Color
  103. cv.CvtColor(im, imC, cv.CV_GRAY2BGR)
  104. markers = array2cv(np.array(m, dtype=np.int32))
  105. # cv.CreateImage(cv.GetSize(im), cv.IPL_DEPTH_32S, 1)
  106. cv.Watershed(imC, markers)
  107. w = cv2array(markers)[:, :, 0]
  108. return w
  109. except ImportError:
  110. HAS_CV = False
  111. letterKeys = """a: Change to Add/Delete Mode
  112. b: Background Subtraction
  113. c: Change to Center Mode (select wound center for post-processing)
  114. d: Change to Draw Mode
  115. e: Change to Extra Seed Mode
  116. f: Force Remake of Seeds From Centroids of Previous Frame
  117. (Shift-F directly copies the seeds from the previous frame)
  118. g: Gaussian Filter
  119. h: Sharpen Filter
  120. i: Invert Image (Display purposes only - underlying data does not change)
  121. j: Join selected seeds (after lasso)
  122. k: Median Filter
  123. l: Change to lasso mode
  124. m: Change to Move Mode (Allows pan/zoom from toolbar)
  125. n: Next Frame
  126. o: Open Images For Segmentation
  127. p: Previous Frame (Shift-P goes to the last visited frame)
  128. q: Quit
  129. r: Reset all image processing (notably Gaussian)
  130. s: Save
  131. t: Toggle overlays (useful when outline is blocking image)
  132. u: Undo Seed Action
  133. v: Change the value of a region (highlighted in e or x mode)
  134. w: Run watershed (automatic now) - Refreshes Map Plot also...
  135. x: Change to Switch Regions Mode
  136. y: Special "Do Nothing" watershed... For use on bad frames
  137. so frame after after will retain seeds from frame before."""
  138. # Options for specific users... let me know if you want a
  139. # specific set of parameters, too...
  140. username = os.path.split(os.path.expanduser("~"))[-1]
  141. _USER_OPTIONS_TABLE = {
  142. # username : [DONT_PANIC, DEFAULT_SEED_SIZE, USE_DEBUG_PRINT, STEAL_B_KEY, CAT_SPACED_ARGS,],
  143. None: [False, 3, False, False, True],
  144. "mashbudn": [False, 2, True, False, False],
  145. "Holley": [True, 2, False, False, True],
  146. "Aroshan": [False, 3, False, True, True],
  147. "Xena": [False, 1, False, False, True],
  148. }
  149. (DONT_PANIC, DEFAULT_SEED_SIZE, USE_DEBUG_PRINT, STEAL_B_KEY, CAT_SPACED_ARGS) = (
  150. _USER_OPTIONS_TABLE[username]
  151. if username in _USER_OPTIONS_TABLE
  152. else _USER_OPTIONS_TABLE[None]
  153. )
  154. def dprint(a):
  155. """Debug print"""
  156. if USE_DEBUG_PRINT:
  157. print(a)
  158. txtHeader = [
  159. "# SeedWater notes file version 1",
  160. "# This file is the generated notes from SeedWater.",
  161. "# If you edit it by hand, do not change the frame headers!",
  162. ]
  163. txtsepA = "# ---------------- Frame "
  164. txtsepB = " ----------------"
  165. def deletecases(l, valList):
  166. if not hasattr(valList, "__iter__"):
  167. valList = [valList]
  168. l = copy(l) # work on a copy
  169. for v in valList:
  170. for i in range(l.count(v)):
  171. l.remove(v)
  172. return l
  173. def GetPointsAtRadius(arr_shape, x, y, r):
  174. """Create a binary image of a circle radius r"""
  175. w, h = arr_shape
  176. r2 = int(r ** 2)
  177. l = []
  178. for i in range(int(x - r), int(x + r + 1)):
  179. for j in range(int(y - r), int(y + r + 1)):
  180. if (i - x) ** 2 + (j - y) ** 2 < r2:
  181. if 0 <= i < w and 0 <= j < h:
  182. l += [[i, j]]
  183. return l
  184. def ImageCircle(r):
  185. """Create a binary image of a circle radius r"""
  186. im = np.zeros([2 * r - 1, 2 * r - 1], dtype=np.int)
  187. r2 = r ** 2
  188. for i in range(2 * r - 1):
  189. for j in range(2 * r - 1):
  190. if (i - r + 1) ** 2 + (j - r + 1) ** 2 < r2:
  191. im[i, j] = 1
  192. else:
  193. im[i, j] = 0
  194. return im
  195. def blitCircleToArray(arr, x, y, r, val):
  196. xL, xH = np.clip(x - r + 1, 0, arr.shape[0]), np.clip(x + r, 0, arr.shape[0])
  197. yL, yH = np.clip(y - r + 1, 0, arr.shape[1]), np.clip(y + r, 0, arr.shape[1])
  198. xcL, xcH = xL - (x - r + 1), 2 * r - 1 + xH - (x + r)
  199. ycL, ycH = yL - (y - r + 1), 2 * r - 1 + yH - (y + r)
  200. # print((xL,xH,yL,yH),(xcL,xcH,ycL,ycH))
  201. # print(xH-xL,yH-yL,xcH-xcL,ycH-ycL)
  202. c = ImageCircle(r)[xcL:xcH, ycL:ycH]
  203. # print(arr[xL:xH,yL:yH].shape,c.shape)
  204. arr[xL:xH, yL:yH] *= 1 - c
  205. arr[xL:xH, yL:yH] += val * c
  206. def MaxMinFinder(x, useMin=True):
  207. sl = [slice(None, -1), slice(None), slice(1, None)]
  208. sl2 = [slice(1, None), slice(None), slice(None, -1)]
  209. # Create 9 arrays offset +-1 in each directions (neighbors)
  210. xNeigh = np.zeros([9] + list(x.shape))
  211. for i in range(9):
  212. xNeigh[i, sl[i // 3], sl[i % 3]] = x[sl2[i // 3], sl2[i % 3]]
  213. xNeigh[4] = 0 # set self to 0 -- 4 because 3*1+1=4 (1,1)
  214. maxMap = (x * 0 + 1).astype(np.bool)
  215. for i in range(9):
  216. if i != 4:
  217. if useMin:
  218. maxMap = maxMap * (x < xNeigh[i])
  219. else:
  220. maxMap = maxMap * (x > xNeigh[i])
  221. return maxMap
  222. # Thanks Wikipedia!!
  223. # A line-drawing algorithm by the pixel...
  224. def BresenhamFunction(p0, p1):
  225. [x0, y0], [x1, y1] = p0, p1
  226. steep = abs(y1 - y0) > abs(x1 - x0)
  227. if steep:
  228. x0, y0 = y0, x0 # swap
  229. x1, y1 = y1, x1 # swap
  230. if x0 > x1:
  231. x0, x1 = x1, x0 # swap
  232. y0, y1 = y1, y0 # swap
  233. deltax = x1 - x0
  234. deltay = abs(y1 - y0)
  235. error = deltax / 2
  236. y = y0
  237. if y0 < y1:
  238. ystep = 1
  239. else:
  240. ystep = -1
  241. l = []
  242. for x in range(x0, x1 + 1):
  243. if steep:
  244. l.append([y, x])
  245. else:
  246. l.append([x, y])
  247. error = error - deltay
  248. if error < 0:
  249. y = y + ystep
  250. error = error + deltax
  251. return l
  252. def CalcPolyPerimeter(pts):
  253. d = 0
  254. for i in range(len(pts)):
  255. d += np.sqrt(
  256. (pts[i][0] - pts[i - 1][0]) ** 2 + (pts[i][1] - pts[i - 1][1]) ** 2
  257. )
  258. return d
  259. def GetMinPoly(pts):
  260. minDist = 1e10
  261. minPoly = []
  262. # Brute Force; hey, at least it works!
  263. for p in itertools.permutations(pts):
  264. newDist = CalcPolyPerimeter(p)
  265. if newDist < minDist:
  266. minDist = newDist
  267. minPoly = p
  268. return minPoly
  269. def dilateByCircle(l, arr_shape, r):
  270. l_old = copy(l)
  271. for i in l_old:
  272. print(i)
  273. els = GetPointsAtRadius(arr_shape, i[0], i[1], r)
  274. for el in els:
  275. if not el in l:
  276. l.append(el)
  277. def CreateOutlines(arr, walgorithm="PyMorph"):
  278. if walgorithm in [
  279. "OpenCV"
  280. ]: # This is now deprecated and will not produce correct results on saving!
  281. return arr == -1
  282. elif walgorithm in ["PyMorph", "Dummy"]: # Later add CV2PM
  283. newArr = np.zeros(arr.shape)
  284. newArr[:-1, :-1] = (np.diff(arr, axis=0)[:, :-1] != 0) + (
  285. np.diff(arr, axis=1)[:-1] != 0
  286. )
  287. return newArr
  288. else:
  289. raise ValueError(walgorithm + " is not a valid watershed algorithm!")
  290. def CreateThickOutlines(arr, thickness=1):
  291. newArr = np.zeros(arr.shape, dtype=arr.dtype)
  292. newArr[:-1] |= np.diff(arr, axis=0) != 0
  293. newArr[1:] |= np.diff(arr, axis=0) != 0
  294. newArr[:, :-1] |= np.diff(arr, axis=1) != 0
  295. newArr[:, 1:] |= np.diff(arr, axis=1) != 0
  296. if thickness > 1:
  297. newArr = scipy.ndimage.morphology.binary_dilation(
  298. newArr, iterations=thickness - 1
  299. )
  300. return newArr
  301. def _try_ints(i):
  302. try:
  303. return int(i)
  304. except:
  305. return long(i) # for python 2 only
  306. IorN = lambda i: None if i == "None" else _try_ints(i)
  307. ForN = lambda f: None if f == "None" else float(f)
  308. def GetReportPixel(sfORwd): # create and return the report_pixel function...
  309. def report_pixel(x_in, y_in):
  310. x, y = int(x_in), int(y_in)
  311. if isinstance(sfORwd, SegmenterFrame):
  312. wd = sfORwd.wd
  313. elif isinstance(sfORwd, WatershedDataCoreWithStats):
  314. wd = sfORwd
  315. else:
  316. raise TypeError("sfORwd must be a WatershedData or SegmenterFrame Object!")
  317. s = wd.watershed[wd.index].shape
  318. if 0 < x < s[1] and 0 < y < s[0]:
  319. value = wd.watershed[wd.index][y, x]
  320. return "value={} x={} y={}".format(value, x, y)
  321. else:
  322. return "x={} y={}".format(x, y)
  323. return report_pixel
  324. def sumN(l):
  325. """Sum that counts None as 0's"""
  326. return sum([(0 if i is None else i) for i in l])
  327. def meanN(l):
  328. """Mean that counts None as 0's"""
  329. return sumN(l) * 1.0 / len(l)
  330. def meanN_skip(l):
  331. return sumN(l) * 1.0 / sum([(0 if i is None else 1) for i in l])
  332. def CreateTimeAxis(timeIntervals, gapIntervals, numberFramesPerSeries, numberOfFrames):
  333. if not (
  334. len(timeIntervals) == (len(gapIntervals) + 1) == len(numberFramesPerSeries)
  335. ):
  336. print(
  337. "intervalList and numberWintervalList must have the same length to be valid!!!"
  338. )
  339. print("gapIntervals must be one element shorter than the others!")
  340. return
  341. # Example:
  342. # timeIntervals = [10,20]
  343. # gapIntervals=[21] # Should be one less
  344. # numberFramesPerSeries=[30,60]
  345. if sum(numberFramesPerSeries) < numberOfFrames:
  346. numberFramesPerSeries[-1] = numberOfFrames - sum(numberFramesPerSeries[:-1])
  347. timeAxis = []
  348. for i in range(len(timeIntervals)):
  349. ran = np.arange(
  350. 0, timeIntervals[i] * numberFramesPerSeries[i], timeIntervals[i]
  351. )
  352. if i == 0:
  353. add = 0
  354. else:
  355. add = timeAxis[-1] + gapIntervals[i - 1]
  356. timeAxis += (ran + add).tolist()
  357. return timeAxis
  358. def GetPlacementName(num):
  359. if num == 1:
  360. return "1st"
  361. elif num == 2:
  362. return "2nd"
  363. elif num == 3:
  364. return "3rd"
  365. else:
  366. return str(num) + "th"
  367. # Trying to use this for both area and major/minor axis causes issues...
  368. # wound components are added and ring components are averaged...
  369. # Just something to keep in mind
  370. def GetBinnedValueForExcel(value, name, bins, timeAxis, woundVals):
  371. # THE GOOD WAY :)
  372. value = np.array(value)
  373. m = []
  374. # time,wound,ring1,ring#
  375. valueBinned = [
  376. [["Wound " + name, None, "Time"]]
  377. + [
  378. [p, None, timeAxis[i]]
  379. for i, p in enumerate(map(sumN, value[:, woundVals - np.r_[2]]))
  380. ]
  381. ]
  382. for b in range(len(bins)):
  383. if bins[b] != []:
  384. m.append(list(map(meanN, value[:, bins[b]])))
  385. # [["Test1"],["Test2"],["Test3"]]+
  386. valueBinned.append(value[:, bins[b]].tolist())
  387. for j in range(len(valueBinned[-1])): # loop through time...
  388. valueBinned[-1][j] += [None, timeAxis[j], m[-1][j]]
  389. firstSheet = [
  390. [timeAxis[i], p] + [m[b][i] for b in range(len(bins))]
  391. for i, p in enumerate(map(sumN, value[:, woundVals - np.r_[2]]))
  392. ]
  393. valueBinned.insert(
  394. 0,
  395. [["time", "wound"] + ["ring" + str(i + 1) for i in range(len(bins))]]
  396. + firstSheet,
  397. )
  398. return valueBinned
  399. ## Crappy way... :(
  400. # oldIndex = wd.index
  401. # areaBinned = []
  402. # for i in range(wd.length):
  403. # if wd.seedList[i] in [None,[]]:
  404. # break
  405. # if i>=len(wd.woundCenters):
  406. # print('No Wound Centers After Frame',i,'!')
  407. # break
  408. # wd.index=i
  409. #
  410. # wd.UpdateValuesList()
  411. # area=wd.CalculateArea(doUpdate=False)
  412. # areaBinned.append([])
  413. # for b in bins:
  414. # inds=[]
  415. # for v in b:
  416. # if v in wd.valList:
  417. # inds.append(wd.valList.index(v))
  418. # areaBinned[-1].append( [area[i] for i in inds] )
  419. # wd.index=oldIndex
  420. # operates on the neighbor list in place
  421. def MergeWoundVals(neigh, wvl):
  422. firstWV = wvl[0]
  423. if neigh[firstWV - 2] is None:
  424. neigh[firstWV - 2] = []
  425. # Merge all the wound value's neighbor lists together
  426. for wv in sorted(wvl[1:])[::-1]:
  427. if neigh[wv - 2] is not None:
  428. neigh[firstWV - 2] += neigh[wv - 2]
  429. neigh[wv - 2] = None
  430. # and remove any duplicate values this created (and wound self-references)
  431. neigh[firstWV - 2] = list(set(deletecases(neigh[firstWV - 2], wvl)))
  432. # Now, go through the whole list and convert references for other wv's to firstWV, removing duplicates
  433. for i in range(len(neigh)):
  434. if neigh[i] != None:
  435. neigh[i] = sorted(
  436. list(set([(firstWV if (j in wvl) else j) for j in neigh[i]]))
  437. )
  438. def GetAllValsByFrame(neighborPairsByFrame):
  439. allValsByFrame = []
  440. for neighborPairs in neighborPairsByFrame:
  441. allValsByFrame.append(sorted(list(set([j for i in neighborPairs for j in i]))))
  442. return allValsByFrame
  443. # Implement these later... a partially-changed version is in Python folder...
  444. class WoundContours(object):
  445. WNv = []
  446. WNl = []
  447. NNv = []
  448. NNl = []
  449. NOv = []
  450. NOl = []
  451. def Extend(self):
  452. for i in [self.WNv, self.WNl, self.NNv, self.NNl, self.NOv, self.NOl]:
  453. i.append([])
  454. def Fill(self, wc):
  455. for i in [self.WNv, self.WNl, self.NNv, self.NNl, self.NOv, self.NOl]:
  456. if len(i) == 0:
  457. print("Lists have no elements!")
  458. return
  459. self.WNv = wc.WNv
  460. self.WNl = wc.WNl
  461. self.NNv = wc.NNv
  462. self.NNl = wc.NNl
  463. self.NOv = wc.NOv
  464. self.NOl = wc.NOl
  465. def SeedListToSparse(seeds, vals, shape):
  466. if seeds is None:
  467. return None
  468. else:
  469. row, col = np.array(seeds).T
  470. sparse = scipy.sparse.coo_matrix((vals, (row, col)), shape=shape).tolil()
  471. return sparse
  472. def GetMapPlotRandomArray():
  473. np.random.seed(0)
  474. mapPlotRandomArray = np.array(
  475. [
  476. np.random.random(10000) * 236 + 20,
  477. np.random.random(10000) * 236 + 20,
  478. np.random.random(10000) * 236 + 20,
  479. ],
  480. dtype=np.uint8,
  481. )
  482. mapPlotRandomArray[:, 0] = 255
  483. mapPlotRandomArray[:, 1] = 255
  484. return mapPlotRandomArray
  485. def LoadSeedPointsFile(seedPointsFile):
  486. # use python's load_module function to get the data instead...
  487. fid = open(seedPointsFile, "U")
  488. try:
  489. Seeds = imp.load_module("Seeds", fid, seedPointsFile, (".py", "U", 1))
  490. except:
  491. wx.MessageBox("Bad Seeds.py file!")
  492. fid.close()
  493. for attr in ("seedList", "seedVals"):
  494. if not hasattr(Seeds, attr):
  495. wx.MessageBox(attr + " is not defined in this Seeds.py!")
  496. return
  497. return Seeds
  498. # A clunkier way to do it...
  499. # oldPath=sys.path
  500. # sys.path = [os.path.split(seedPointsFile)[0]]
  501. # imp.reload(Seeds)
  502. # sys.path=oldPath
  503. # Really old method:
  504. # fid = open(seedPointsFile)
  505. # try:
  506. # exec(fid.read().replace('\r','')) # Loads seedList and seedVals from the file...
  507. # except SyntaxError:
  508. # print('Invalid Syntax in Seeds.py')
  509. # fid.close()
  510. # Seeds.seedList = seedList
  511. # Seeds.seedVals = seedVals
  512. def WriteSeedPointsFile(seedPointsFile, seedList, seedVals, walgorithm, woundCenters):
  513. seedListStr = repr(seedList) # .replace('[[[','['+os.linesep+'[[') \
  514. # .replace('[[','[ [') \
  515. # .replace(']]]','] ]'+os.linesep+']') \
  516. # .replace(']], ','] ],'+os.linesep) \
  517. # .replace('], ','],'+os.linesep+' ')
  518. seedValsStr = repr(seedVals) # .replace('[[','['+os.linesep+'[') \
  519. # .replace(']',']'+os.linesep+']') \
  520. # .replace(', ',','+os.linesep+' ')
  521. walgorithmStr = repr(walgorithm)
  522. woundCentersStr = repr(woundCenters)
  523. fid = open(seedPointsFile, "w")
  524. fid.write("seedList = " + seedListStr.replace("\r", "").replace("\n", ""))
  525. fid.write("\r\n")
  526. fid.write("seedVals = " + seedValsStr.replace("\r", "").replace("\n", ""))
  527. fid.write("\r\n")
  528. fid.write("walgorithm = " + walgorithmStr.replace("\r", "").replace("\n", ""))
  529. fid.write("\r\n")
  530. fid.write("woundCenters = " + woundCentersStr.replace("\r", "").replace("\n", ""))
  531. fid.write("\r\n")
  532. fid.close()
  533. print("Saving seeds for " + str(len(seedList)) + " frames:")
  534. # def MakeCmapFromArray(a):
  535. # r,b,g = [],[],[]
  536. # for i in range(len(a[0])):
  537. # p=i*1./(len(a[0])-1)
  538. # r.append((p,a[0][i],a[0][i]))
  539. # g.append((p,a[1][i],a[1][i]))
  540. # b.append((p,a[2][i],a[2][i]))
  541. # segmentdata = {'red':tuple(r),'green':tuple(g),'blue':tuple(b)}
  542. # return matplotlib.colors.LinearSegmentedColormap('rand4SW',segmentdata)
  543. # This class now deals with image stacks as well as images...
  544. class WatershedDataCore(object):
  545. def __init__(self, arrayIn, previousSeeds=None):
  546. # Change this to allow orig, min-max filtered, and sharpened data
  547. # Need a way to mark the "Un-seeds" that exist in the background only...
  548. if arrayIn.ndim == 2:
  549. arrayIn = arrayIn[np.newaxis, :]
  550. self.length = arrayIn.shape[0] # number of frames
  551. self.shape = arrayIn.shape
  552. # I AM CHANGING THE DEFAULT BEHAVIOR!!!!!!!!!
  553. # I don't really think this will change the watersheds, but it
  554. # really might change the brightness of the image...
  555. if arrayIn.dtype == np.uint16:
  556. self.origData = arrayIn
  557. self.origData *= int((2 ** 16 - 1) / arrayIn.max())
  558. elif arrayIn.dtype == np.uint8:
  559. self.origData = np.array(arrayIn, dtype=np.uint16)
  560. self.origData *= int((2 ** 16 - 1) / arrayIn.max())
  561. # self.origData *= 2**8
  562. else:
  563. self.origData = GTL.DivideConvertType(
  564. arrayIn,
  565. bits=16,
  566. maxVal=2 ** 15 - 1,
  567. zeroMode="stretch",
  568. maxMode="stretch",
  569. )
  570. # 1 full copy...
  571. # bg=gaussian_filter(arrayIn,64)
  572. # self.origData = arrayIn+bg.max()-bg
  573. # self.origData = gaussian_filter(self.origData,2) + gaussian_filter(self.origData,6).max()-gaussian_filter(self.origData,6)
  574. # self.origData = self.origData-self.origData.min()
  575. self.gaussSigma = 0
  576. self.filterData = np.array(self.origData) # 2 full copy
  577. self.sparseList = [
  578. None
  579. ] * self.length # will be a list of sparse matrices (lil format)
  580. self.seedSelections = [None] * self.length # will also be a sparse matrix list
  581. self.notes = [None] * self.length
  582. self.woundCenters = [None] * self.length
  583. self.background = True # Set to True for better results...
  584. self.watershed = np.zeros(self.shape, dtype=np.int16) # 3 full copy
  585. self.seedArray = np.zeros(self.shape[1:], dtype=np.int)
  586. self.woutline = np.zeros(self.shape[1:], dtype=np.int)
  587. self.index = 0
  588. self.lastFrameVisited = 0
  589. # walgorithm should be 'PyMorph', Dummy, ['OpenCV', 'CV2PM']
  590. self.walgorithm = ["PyMorph"] * self.length
  591. self.framesVisited = [False] * self.length
  592. self.framesVisited[self.index] = True
  593. self.rgba = None # Will hold the data for the color plot...
  594. self.rgbaH = None # Will hold the data for the highlight plot...
  595. self.rgbM = None # Will hold the data for the map plot...
  596. self.bgPlot = None
  597. self.overlayVisible = True
  598. self.showInverted = False
  599. self.colorplot = None
  600. self.mapPlot = None
  601. self.mapCentroids = []
  602. self.hiplot = None
  603. self.showWoundCenters = False
  604. self.redDot = None
  605. self.bwDot = None
  606. self.selectionVals = []
  607. self.pointSize = DEFAULT_SEED_SIZE
  608. self.point_mode = False
  609. self.mapPlotRandomArray = GetMapPlotRandomArray()
  610. self.mapPlotRandomFloatArray = (
  611. np.array(self.mapPlotRandomArray, dtype=np.float) / 255.0
  612. )
  613. self.mapPlotCmap = matplotlib.colors.ListedColormap(
  614. self.mapPlotRandomFloatArray.T
  615. )
  616. self.valList = None
  617. # self.mapPlotCmap = MakeCmapFromArray(self.mapPlotRandomFloatArray)
  618. self.previousDrawPoint = None
  619. # For undo...
  620. self.oldSparseList = None
  621. self.oldSeedSelections = None
  622. self.old_point_mode = False
  623. # self.UpdateSeeds()
  624. def SetUndoPoint(self):
  625. # Set old state to be current state before any action
  626. # This allows undo to work...
  627. self.oldSparseList = deepcopy(self.sparseList[self.index])
  628. self.oldSeedSelections = deepcopy(self.seedSelections[self.index])
  629. self.old_point_mode = self.point_mode
  630. self.previousDrawPoint = None
  631. def RemoveUndo(self):
  632. # Remove old state
  633. self.oldSparseList = None
  634. self.oldSeedSelections = None
  635. self.old_point_mode = self.point_mode
  636. def Undo(self):
  637. # Swap old and current state
  638. if None not in [self.oldSparseList, self.oldSeedSelections]:
  639. print("Undoing")
  640. print("SL")
  641. print(self.sparseList[self.index].nnz)
  642. print("oSL")
  643. print(self.oldSparseList.nnz)
  644. self.sparseList[self.index], self.oldSparseList = (
  645. self.oldSparseList,
  646. self.sparseList[self.index],
  647. ) # SWAP
  648. self.seedSelections[self.index], self.oldSeedSelections = (
  649. self.oldSeedSelections,
  650. self.seedSelections[self.index],
  651. ) # SWAP
  652. self.point_mode, self.old_point_mode = (
  653. self.old_point_mode,
  654. self.point_mode,
  655. ) # SWAP
  656. self.previousDrawPoint = None
  657. def SaveTxt(self, filename):
  658. fid = open(filename, "w")
  659. for i in txtHeader:
  660. fid.write(i + "\r\n")
  661. for i, t in enumerate(self.notes):
  662. if t is not None and len(t) > 0:
  663. fid.write(txtsepA + str(i) + txtsepB + "\r\n")
  664. t2 = t.replace(os.linesep, "\n").replace("\r", "").replace("\n", "\r\n")
  665. fid.write(t2 + "\r\n")
  666. def LoadTxt(self, filename):
  667. print("Load Notes")
  668. fid = open(filename, "r")
  669. for i in range(3):
  670. line = (
  671. fid.readline()
  672. .replace(os.linesep, "")
  673. .replace("\n", "")
  674. .replace("\r", "")
  675. )
  676. if line == txtHeader[i]:
  677. pass
  678. else:
  679. print("Invalid Header")
  680. print(line, "is not", txtHeader[i])
  681. return
  682. fnLast = -1
  683. newFrame = False
  684. self.notes = []
  685. lA = len(txtsepA)
  686. lB = len(txtsepB)
  687. for line in fid:
  688. line = line.replace(os.linesep, "").replace("\n", "").replace("\r", "")
  689. # Check for Header line
  690. if len(line) > lA + lB:
  691. if (
  692. line[:lA] == txtsepA
  693. and line[-lB:] == txtsepB
  694. and line[lA:-lB].isdigit()
  695. ):
  696. fn = int(line[lA:-lB])
  697. if fn > fnLast:
  698. self.notes += [""] * (
  699. fn - fnLast
  700. ) # Add entries for each frame to notes
  701. fnLast = fn
  702. newFrame = True
  703. else:
  704. print("Invalid Frame Separators")
  705. print("Frame Separators must be in chronological order!")
  706. return
  707. if fnLast == -1:
  708. print("Should be a Frame Separator right after the Header!")
  709. return
  710. if newFrame:
  711. newFrame = False
  712. else:
  713. self.notes[-1] += line + os.linesep
  714. self.notes += [None] * (self.length - len(self.notes))
  715. print(self.notes)
  716. def SaveAllStats(self):
  717. pass
  718. def LoadStats(self):
  719. pass
  720. def Save(self, d, saveOutlines=True): # Ignore Undo
  721. print("Saving To ", d)
  722. # TODO: FOR SOME WEIRD REASON, SAVE/LOAD only recreates the seeds
  723. # for the first and last saved frame... FIXED I THINK?
  724. # TODO: I need a simple indicator to show whether or not a frame
  725. # has been initiated...
  726. if not os.path.exists(d):
  727. os.mkdir(d)
  728. segmentsD = os.path.join(d, "Segments")
  729. outlinesD = os.path.join(d, "Outlines")
  730. mapsD = os.path.join(d, "Maps")
  731. if not os.path.exists(segmentsD):
  732. os.mkdir(segmentsD)
  733. if not os.path.exists(outlinesD):
  734. os.mkdir(outlinesD)
  735. if not os.path.exists(mapsD):
  736. os.mkdir(mapsD)
  737. segmentsBase = os.path.join(segmentsD, "Segment")
  738. # Py makes it easy to load ;)
  739. seedPointsFile = os.path.join(d, "Seeds.py")
  740. notesFile = os.path.join(d, "Notes.txt")
  741. outlinesBase = os.path.join(outlinesD, "Outline")
  742. GTL.SaveFileSequence(
  743. self.watershed,
  744. basename=segmentsBase,
  745. im_format="tif",
  746. sparseSave=self.framesVisited,
  747. )
  748. if saveOutlines:
  749. getOutlines = lambda arr: CreateOutlines(
  750. arr, walgorithm="PyMorph"
  751. ) # This will break the deprecated OpenCV watershed
  752. GTL.SaveFileSequence(
  753. self.watershed,
  754. basename=outlinesBase,
  755. im_format="tif",
  756. sparseSave=self.framesVisited,
  757. functionToRunOnFrames=getOutlines,
  758. )
  759. # Still just want an easy format to save and load...
  760. cooList = [(None if i is None else i.tocoo()) for i in self.sparseList]
  761. seedList = [
  762. (None if i is None else np.array([i.row, i.col]).T.tolist())
  763. for i in cooList
  764. ]
  765. seedVals = [
  766. (None if i is None else i.data.astype(np.int).tolist()) for i in cooList
  767. ]
  768. WriteSeedPointsFile(
  769. seedPointsFile, seedList, seedVals, self.walgorithm, self.woundCenters
  770. )
  771. for i, s in enumerate(self.sparseList):
  772. if s is None:
  773. print(i, "--")
  774. elif s.nnz == 0:
  775. print("Empty!")
  776. else:
  777. print(i, "initialized")
  778. # Save Notes
  779. self.SaveTxt(notesFile)
  780. # Save Stats
  781. self.SaveAllStats(d)
  782. self.framesVisited = [False] * self.length
  783. self.framesVisited[self.index] = True
  784. def Open(self, d):
  785. segmentsD = os.path.join(d, "Segments")
  786. seedPointsFile = os.path.join(d, "Seeds.py")
  787. notesFile = os.path.join(d, "Notes.txt")
  788. self.watershed[:] = 0
  789. self.woutline[:] = 0
  790. if not os.path.exists(segmentsD):
  791. os.mkdir(segmentsD)
  792. watershedTemp = GTL.LoadFileSequence(segmentsD, "Segment*")
  793. if watershedTemp is not None:
  794. for i in range(min(len(self.watershed), len(watershedTemp))):
  795. self.watershed[i] = watershedTemp[i]
  796. print(self.shape)
  797. print(self.watershed[0].max())
  798. Seeds = LoadSeedPointsFile(seedPointsFile)
  799. if len(Seeds.seedList) == self.length:
  800. for i in range(self.length):
  801. if None in [Seeds.seedList[i], Seeds.seedVals[i]]:
  802. self.sparseList[i] = None
  803. elif [] in [Seeds.seedList[i], Seeds.seedVals[i]]:
  804. self.sparseList[i] = scipy.sparse.lil_matrix(
  805. self.shape[1:], dtype=np.uint16
  806. )
  807. else:
  808. # Older, but more straightforward, more robust, and equally fast way to do this;
  809. # Prevents glitches caused by loading older Seeds.py files...
  810. tempArray = np.zeros(self.shape[1:], np.uint16)
  811. for j, s in enumerate(Seeds.seedList[i]):
  812. tempArray[s[0], s[1]] = Seeds.seedVals[i][j]
  813. self.sparseList[i] = scipy.sparse.lil_matrix(
  814. tempArray, dtype=np.uint16
  815. )
  816. # Newer way to do this, but less robust, and surprisingly not any faster... go figure!
  817. ## row,col = np.array(Seeds.seedList[i]).T
  818. ## vals = Seeds.seedVals[i]
  819. ## self.sparseList[i] = scipy.sparse.coo_matrix((vals,(row,col)), shape=self.shape[1:], dtype=np.uint16).tolil() # I guess I could change the dtype later if I need to...
  820. if i == 0 and self.sparseList[i] is None:
  821. self.sparseList[i] = scipy.sparse.lil_matrix(
  822. self.shape[1:], dtype=np.uint16
  823. )
  824. try: # Since walgorithm is not part of early versions, allow it to be optional
  825. Seeds.walgorithm
  826. except:
  827. Seeds.walgorithm = ["PyMorph"] * self.length
  828. try: # Since woundCenters was not part of version 0.2 and earlier, allow it to be optional
  829. Seeds.woundCenters
  830. except:
  831. Seeds.woundCenters = [None] * self.length
  832. self.walgorithm = Seeds.walgorithm
  833. self.woundCenters = Seeds.woundCenters
  834. print("Loading seeds for " + str(self.length) + " frames:")
  835. for i, s in enumerate(self.sparseList):
  836. if s is None:
  837. print(i, "--")
  838. elif s.nnz == 0:
  839. print("Empty!")
  840. else:
  841. print(i, "initialized")
  842. self.seedSelections = [None] * self.length
  843. for i in range(self.length):
  844. if self.sparseList[i] is not None:
  845. self.seedSelections[i] = scipy.sparse.lil_matrix(
  846. self.shape[1:], dtype=np.bool
  847. )
  848. # This is technically unnecessary because it only gets called
  849. # on new WatershedData's anyway and those are always initialized to frame 0
  850. lastActiveFrame = self.GetLastActiveFrame()
  851. if self.index > lastActiveFrame:
  852. self.index = lastActiveFrame
  853. self.UpdateSeeds()
  854. # Load Notes
  855. if os.path.exists(notesFile):
  856. self.LoadTxt(notesFile)
  857. # Load Stats
  858. self.LoadStats(d)
  859. else:
  860. print("These seeds do not match the images open!")
  861. print("Images length:", self.length, " Seeds length:", len(Seeds.seedList))
  862. self.framesVisited = [False] * self.length
  863. self.framesVisited[self.index] = True
  864. self.RemoveUndo()
  865. if len(Seeds.seedList) == self.length:
  866. return True
  867. def UpdateSeeds(self, force=False):
  868. """Update seedArray from either local minima or seedList"""
  869. if force == True:
  870. self.SetUndoPoint()
  871. maxMap = MaxMinFinder(
  872. gaussian_filter(self.origData[self.index], self.gaussSigma)
  873. ) # Max pts of gauss filtered image (single pixels with values of 1)
  874. row, col = np.where(maxMap)
  875. self.seedArray[:] = 0
  876. for i in range(len(row)):
  877. self.seedArray[row[i], col[i]] = (
  878. i + 2
  879. ) # 0 and 1 are reserved for unfilled region and background value respectively
  880. self.seedArray = scipy.ndimage.morphology.grey_dilation(
  881. self.seedArray, footprint=ImageCircle(DEFAULT_SEED_SIZE)
  882. )
  883. self.sparseList[self.index] = scipy.sparse.lil_matrix(
  884. self.seedArray, dtype=np.uint16
  885. )
  886. self.seedSelections[self.index] = scipy.sparse.lil_matrix(
  887. self.shape[1:], dtype=np.bool
  888. )
  889. else:
  890. self.seedArray = self.sparseList[self.index].toarray()
  891. if self.background:
  892. val = (
  893. 2
  894. ) # (2 if self.walgorithm=='cv' else 1) # I could do this, but what's the need?
  895. self.seedArray[:val, :] = 1
  896. self.seedArray[-val:, :] = 1
  897. self.seedArray[:, :val] = 1
  898. self.seedArray[:, -val:] = 1
  899. self.Watershed()
  900. def Watershed(self, algorithm="PyMorph"): # Ignore Undo
  901. if not HAS_CV and algorithm in "OpenCV":
  902. print("OpenCV is not installed!")
  903. print("Use PyMorph algorithm instead!")
  904. return
  905. if algorithm in ["OpenCV", "PyMorph", "Dummy"]: # Later add CV2PM
  906. self.walgorithm[self.index] = algorithm
  907. else:
  908. print(algorithm, "is not a valid watershed algorithm!")
  909. return
  910. # Run the real watershed algorithm
  911. if self.walgorithm[self.index] == "OpenCV":
  912. self.watershed[self.index] = cvWater(
  913. GTL.DivideConvertType(self.filterData[self.index], 8), self.seedArray
  914. )
  915. elif self.walgorithm[self.index] == "PyMorph":
  916. # self.watershed[self.index] = pmWatershed.cwatershed(
  917. # Change to use mahotas ... should be faster...
  918. self.watershed[self.index] = mahotas.cwatershed(
  919. self.filterData[self.index], self.seedArray
  920. )
  921. elif self.walgorithm[self.index] == "Dummy":
  922. # Just set watershed directly to seeds
  923. self.watershed[self.index] = self.seedArray
  924. # Change 0 (unfilled) to 1 (background)
  925. w = np.where(self.watershed[self.index] == 0)
  926. self.watershed[self.index][w] = 1
  927. else:
  928. print(self.walgorithm[self.index], "is not a valid watershed algorithm!")
  929. self.woutline = CreateOutlines(
  930. self.watershed[self.index], walgorithm=self.walgorithm[self.index]
  931. )
  932. def UpdateValuesList(self, index=None):
  933. if index is None:
  934. index = self.index
  935. self.valList = []
  936. for v in range(2, self.watershed[index].max() + 1):
  937. if np.any(self.watershed[index] == v):
  938. self.valList.append(v)
  939. def MakeSeedsFromPrevious(self):
  940. if self.index > 0:
  941. self.SetUndoPoint()
  942. self.seedArray[:] = 0
  943. cmList = self.GetCentroids(self.index - 1)
  944. for cm in cmList:
  945. cm = [int(round(cm[0])), int(round(cm[1]))] # Convert to int
  946. self.seedArray[cm[0], cm[1]] = self.watershed[self.index - 1][
  947. cm[0], cm[1]
  948. ]
  949. self.seedArray = scipy.ndimage.morphology.grey_dilation(
  950. self.seedArray, footprint=ImageCircle(DEFAULT_SEED_SIZE)
  951. )
  952. self.sparseList[self.index] = scipy.sparse.lil_matrix(self.seedArray)
  953. self.seedSelections[self.index] = scipy.sparse.lil_matrix(
  954. self.shape[1:], dtype=np.bool
  955. )
  956. self.UpdateSeeds()
  957. self.Watershed()
  958. def CopySeedsFromPrevious(self):
  959. if self.index > 0:
  960. self.SetUndoPoint()
  961. self.sparseList[self.index] = deepcopy(self.sparseList[self.index - 1])
  962. self.seedSelections[self.index] = deepcopy(
  963. self.seedSelections[self.index - 1]
  964. )
  965. self.UpdateSeeds()
  966. self.Watershed()
  967. def GetLastActiveFrame(self):
  968. lastActiveFrame = self.length - 1
  969. for i in range(self.length):
  970. if self.sparseList[i] is None:
  971. lastActiveFrame = i - 1
  972. break
  973. return lastActiveFrame
  974. def NextFrame(self):
  975. if self.index + 1 < self.length:
  976. self.lastFrameVisited = self.index
  977. self.index += 1
  978. print("Move to Frame", self.index)
  979. if self.sparseList[self.index] is None:
  980. self.MakeSeedsFromPrevious()
  981. else:
  982. self.UpdateSeeds()
  983. self.RemoveUndo()
  984. else:
  985. print("No more frames!!!!")
  986. def PreviousFrame(self):
  987. if self.index > 0:
  988. self.lastFrameVisited = self.index
  989. self.index -= 1
  990. print("Move to Frame", self.index)
  991. self.UpdateSeeds()
  992. else:
  993. print("At the beginning.")
  994. self.RemoveUndo()
  995. def MoveToFrame(self, newIndex):
  996. if newIndex < 0:
  997. newIndex = 0
  998. if newIndex >= self.length:
  999. newIndex = self.length - 1
  1000. lastActiveFrame = self.GetLastActiveFrame()
  1001. if newIndex < lastActiveFrame + 1:
  1002. self.lastFrameVisited = self.index
  1003. self.index = newIndex # hop ahead, then find new seeds...
  1004. print("Move to Frame", self.index)
  1005. self.UpdateSeeds()
  1006. self.RemoveUndo()
  1007. elif newIndex == lastActiveFrame:
  1008. self.lastFrameVisited = self.index
  1009. self.index = newIndex # hop ahead, then find new seeds...
  1010. print("Move to Frame", self.index)
  1011. self.MakeSeedsFromPrevious()
  1012. self.RemoveUndo()
  1013. else: # newIndex>lastActiveFrame+1
  1014. print("You can't move that far ahead yet!")
  1015. def Gauss(self, rad):
  1016. """Apply Gaussian Blur to data"""
  1017. self.gaussSigma = rad
  1018. self.RemoveUndo()
  1019. def ResetData(self):
  1020. """Return data to original data"""
  1021. self.gaussSigma = 0
  1022. self.filterData[self.index] = np.array(self.origData[self.index])
  1023. self.RemoveUndo()
  1024. def BGSubtract(self, rad):
  1025. """Apply Gaussian Blur to data"""
  1026. bg = gaussian_filter(self.filterData[self.index], rad)
  1027. self.filterData[self.index] = self.filterData[self.index] + bg.max() - bg
  1028. self.filterData[self.index] -= self.filterData[self.index].min()
  1029. self.RemoveUndo()
  1030. def Median(self, filterSize):
  1031. self.filterData[self.index] = median_filter(
  1032. self.filterData[self.index], int(filterSize)
  1033. )
  1034. self.RemoveUndo()
  1035. def Sharpen(self, r1, r2):
  1036. """Apply Gaussian Blur to data"""
  1037. g1 = gaussian_filter(self.filterData[self.index], r1)
  1038. g2 = gaussian_filter(self.filterData[self.index], r2)
  1039. self.filterData[self.index] = g1 + g2.max() - g2
  1040. self.filterData[self.index] -= self.filterData[self.index].min()
  1041. self.RemoveUndo()
  1042. def OutOfBounds(self, point):
  1043. s = self.origData[self.index].shape
  1044. return point[0] < 0 or point[1] < 0 or point[0] >= s[0] or point[1] >= s[1]
  1045. def UpdatePointsWithVal(self, wh, val): # Raw update given points and values
  1046. self.seedArray[wh] = val
  1047. self.sparseList[self.index] = scipy.sparse.lil_matrix(
  1048. self.seedArray, dtype=np.uint16
  1049. )
  1050. self.seedSelections[self.index] = scipy.sparse.lil_matrix(
  1051. self.shape[1:], dtype=np.bool
  1052. )
  1053. self.UpdateSeeds()
  1054. def NewSeed(self, pointIn, val=None):
  1055. """Add a new seed point"""
  1056. if self.OutOfBounds(pointIn):
  1057. return False
  1058. self.SetUndoPoint()
  1059. newPoints = GetPointsAtRadius(
  1060. self.seedArray.shape, pointIn[0], pointIn[1], self.pointSize
  1061. )
  1062. if val is None:
  1063. m = 2 # 0 is unassigned and 1 is for background...
  1064. for l in self.sparseList:
  1065. if l is not None:
  1066. if l.nnz != 0:
  1067. m = max(m, l.tocoo().data.max())
  1068. val = m + 1
  1069. points = np.array(newPoints).T
  1070. wh = (points[0], points[1])
  1071. self.UpdatePointsWithVal(wh, val)
  1072. return True
  1073. def ExtraSeed(self, pointIn, val):
  1074. return self.NewSeed(pointIn, val=val)
  1075. def ExtraSeedLine(self, point0, point1, val):
  1076. newPoints = BresenhamFunction(point0, point1)
  1077. if self.pointSize == 1: # Make 1-pixel lines
  1078. pass
  1079. else: # Make 2-pixel lines
  1080. dilateByCircle(newPoints, self.watershed[0].shape, 1.9)
  1081. points = np.array(newPoints).T
  1082. wh = (points[0], points[1])
  1083. self.UpdatePointsWithVal(wh, val)
  1084. def DeleteSeedByRegion(self, point):
  1085. """Remove the seed point from the list"""
  1086. self.SetUndoPoint()
  1087. if self.watershed[self.index] is not None:
  1088. val = self.watershed[self.index, point[0], point[1]]
  1089. wh = np.where(self.seedArray == val)
  1090. self.UpdatePointsWithVal(wh, 0)
  1091. return True
  1092. else:
  1093. return False
  1094. def DeleteSeed(self, point):
  1095. """Remove the seed point from the list"""
  1096. self.SetUndoPoint()
  1097. if self.seedArray[point[0], point[1]] != 0:
  1098. self.UpdatePointsWithVal((np.array([point[0]]), np.array([point[1]])), 0)
  1099. return True
  1100. else:
  1101. return False
  1102. def DeleteSelectedSeeds(self, invertSelections=False):
  1103. self.SetUndoPoint()
  1104. wh = np.where(self.seedSelections[self.index].toarray() ^ invertSelections)
  1105. self.UpdatePointsWithVal(wh, 0)
  1106. def MergeSelectedSeeds(self):
  1107. self.SetUndoPoint()
  1108. # get the val to change to:
  1109. wh = np.where(self.seedArray * self.seedSelections[self.index].toarray())
  1110. newVal = min(self.seedArray[wh])
  1111. self.UpdatePointsWithVal(wh, newVal)
  1112. def ChangeRegionValue(self, vOld, vNew):
  1113. # I think it is probaly the right thing to add undo here, but it sure
  1114. # Does make things quirky b/c only changes in seeds are recorded in Undo...
  1115. # self.SetUndoPoint()
  1116. self.RemoveUndo()
  1117. wh = np.where(self.seedArray == vOld)
  1118. self.UpdatePointsWithVal(wh, vNew)
  1119. def SwitchRegionValues(self, v1, v2):
  1120. # I think it is probaly the right thing to add undo here, but it sure
  1121. # Does make things quirky b/c only changes in seeds are recorded in Undo...
  1122. # self.SetUndoPoint()
  1123. self.RemoveUndo()
  1124. wh1 = np.where(self.seedArray == v1)
  1125. wh2 = np.where(self.seedArray == v2)
  1126. self.UpdatePointsWithVal(wh1, v2)
  1127. self.UpdatePointsWithVal(wh2, v1)
  1128. def UpdateSelection(self, point=None, append=False):
  1129. if append == False:
  1130. self.selectionVals = []
  1131. if point is not None:
  1132. self.selectionVals += [self.watershed[self.index, point[0], point[1]]]
  1133. self.point_mode = False
  1134. def CompressSeedValues(self):
  1135. oldIndex = self.index
  1136. numFrames = self.length
  1137. for i in range(self.length):
  1138. if self.sparseList[i] is None:
  1139. numFrames = i
  1140. break
  1141. usedVals = []
  1142. for v in range(2, self.watershed.max() + 1):
  1143. if np.any(self.watershed == v):
  1144. usedVals.append(v)
  1145. usedVals.sort() # sort the list
  1146. print(usedVals)
  1147. newVal = 2
  1148. for val in usedVals:
  1149. if val != newVal:
  1150. for i in range(numFrames):
  1151. print("frame", i)
  1152. self.index = i
  1153. if self.oldSparseList is not None:
  1154. oldSeedArray = self.oldSparseList.toarray()
  1155. oldSeedArray[np.where(oldSeedArray == val)] = newVal
  1156. self.oldSparseList = scipy.sparse.lil_matrix(
  1157. oldSeedArray, dtype=np.uint16
  1158. )
  1159. self.seedArray[np.where(self.seedArray == val)] = newVal
  1160. self.sparseList[i] = scipy.sparse.lil_matrix(
  1161. self.seedArray, dtype=np.uint16
  1162. )
  1163. l = self.selectionVals
  1164. if l not in [[], None]:
  1165. while val in l:
  1166. l[l.index(val)] = newVal
  1167. self.UpdateSeeds() # takes care of watershed...
  1168. self.UpdateValuesList()
  1169. self.UpdateSelection()
  1170. self.framesVisited[i] = True
  1171. newVal += 1
  1172. self.index = oldIndex
  1173. def AutoCenterWound(self, woundVals):
  1174. oldIndex = self.index
  1175. for self.index in range(self.length): # For each frame
  1176. if self.sparseList[self.index] is not None:
  1177. newWVal = (
  1178. 1e6
  1179. ) # I don't think anyone will ever create a million cells...
  1180. wi = np.array(self.watershed[self.index], dtype=np.int)
  1181. for v in woundVals:
  1182. wi[np.where(wi == v)] = newWVal
  1183. test = wi == newWVal
  1184. if np.any(test):
  1185. self.woundCenters[self.index] = center_of_mass(
  1186. test.astype(np.float)
  1187. )[::-1]
  1188. # else:
  1189. # self.woundCenters[self.index] = (0,0) # just define something so the thing doesn't fail all the time?
  1190. self.index = oldIndex
  1191. def GetManualInputs(self, d):
  1192. if not os.path.exists(d):
  1193. print("ManualInputs.py not found!")
  1194. return
  1195. ManualInputs = os.path.join(d, "ManualInputs.py")
  1196. if not os.path.exists(ManualInputs):
  1197. te = wx.TextEntryDialog(
  1198. None,
  1199. "Enter values for the following;\nThey will then be saved in ManualInputs.py:",
  1200. "ManualInputs.py",
  1201. "woundVals=[]\n"
  1202. "timeIntervals=[]\n"
  1203. "numberFramesPerSeries=[]\n"
  1204. "gapIntervals=[]\n",
  1205. style=wx.TE_MULTILINE,
  1206. )
  1207. te.SetInitialSize(
  1208. size=(-1, 250)
  1209. ) # set initial size of the window to be 300
  1210. te.ShowModal()
  1211. fid = open(ManualInputs, "w")
  1212. fid.write(te.GetValue())
  1213. fid.close()
  1214. # fid = open(ManualInputs,'r')
  1215. # exec(fid.read().replace('\r',''))
  1216. # fid.close()
  1217. fid = open(ManualInputs, "U")
  1218. MI = imp.load_module("ManualInputs", fid, "ManualInputs.py", (".py", "U", 1))
  1219. fid.close()
  1220. try:
  1221. MI.woundVals
  1222. MI.timeIntervals
  1223. MI.gapIntervals
  1224. MI.numberFramesPerSeries
  1225. except:
  1226. print(
  1227. "All the variables (woundVals,timeIntervals,gapIntervals,numberFramesPerSeries) must be defined!"
  1228. )
  1229. return
  1230. return MI
  1231. def SaveCSV(name, directory, data): # This is terrrrible... fix it later!!
  1232. s = (
  1233. repr(data)
  1234. .replace("\r", "")
  1235. .replace("\n", "")
  1236. .replace("(", "[")
  1237. .replace(")", "]")
  1238. .replace("[[", "")
  1239. .replace("]]", "")
  1240. .replace("[", "")
  1241. .replace("], ", "\r\n")
  1242. )
  1243. with open(os.path.join(directory, name + ".csv"), "w") as fid:
  1244. fid.write(s)
  1245. def _data_splitter_for_excel(data, adjLength):
  1246. print("Over 256 frames!")
  1247. print("Ask user how to save xls file...")
  1248. s = "\n".join(
  1249. [
  1250. "There are too many frames ({}) for one excel sheet (max 256)!".format(
  1251. adjLength
  1252. ),
  1253. "Enter the number of frames to skip between saving.",
  1254. "(A value of 1 means save all the data, 2 is save every other frame, etc...)",
  1255. "Otherwise, the data will be broken into 256 frame files.",
  1256. ]
  1257. )
  1258. teDialog = wx.TextEntryDialog(None, s, caption="Skip Frames?", defaultValue="1")
  1259. dia = teDialog.ShowModal()
  1260. if dia == wx.ID_CANCEL:
  1261. print("Cancel, save all frames")
  1262. skip_val = 1
  1263. elif dia == wx.ID_OK:
  1264. skip_val = teDialog.GetValue()
  1265. if skip_val.isdigit():
  1266. skip_val = int(skip_val)
  1267. if skip_val > 1:
  1268. print("Only save every", skip_val, "frames.")
  1269. else:
  1270. skip_val = 1
  1271. print("Only save every", skip_val, "frames.")
  1272. else:
  1273. print("Bad entry! Just save all frames!")
  1274. skip_val = 1
  1275. if skip_val > 1:
  1276. data = [d[::skipVal] for d in data]
  1277. numBreakups = 1
  1278. if (
  1279. len(data[0]) > 256
  1280. ): # This will break the excel write, so make break into pieces...
  1281. numBreakups = (len(data[0]) - 1) / 256 + 1
  1282. indexList = [256 * i for i in range(numBreakups)] + [len(data[0])]
  1283. dataSets = [
  1284. [d[indexList[f] : indexList[f + 1]] for d in data] # each parameter
  1285. for f in range(numBreakups)
  1286. ] # each file
  1287. else:
  1288. dataSets = [data]
  1289. return dataSets, skip_val, numBreakups
  1290. # WatershedDataCoreWithStats adds stat calculation functions to WatershedDataCore
  1291. class WatershedDataCoreWithStats(WatershedDataCore):
  1292. def __init__(self, *args, **kwds):
  1293. """Initialize the Watershed object, including statistical measures
  1294. Make sure not to overwrite any of these below (set to = [])
  1295. or the stats groups will no longer function --
  1296. Instead, use stat[:] = ..."""
  1297. WatershedDataCore.__init__(self, *args, **kwds)
  1298. self.valuesByFrame = []
  1299. self.centroidX = []
  1300. self.centroidY = []
  1301. self.xmin = []
  1302. self.xmax = []
  1303. self.ymin = []
  1304. self.ymax = []
  1305. self.area = []
  1306. self.perimeter = []
  1307. self.major = []
  1308. self.minor = []
  1309. self.angle = []
  1310. self.woundDistance = []
  1311. self.woundAngle = []
  1312. self.edgeBrightnessValues = []
  1313. self.neighbors = []
  1314. self.edge_brightness_name = "EdgeBrightnessValues"
  1315. self.neighbor_py_name = "Neighbors.py"
  1316. excel_stats_table = (
  1317. (self.valuesByFrame, "CellValuesByFrame"),
  1318. (self.centroidX, "CentroidX"),
  1319. (self.centroidY, "CentroidY"),
  1320. (self.xmin, "Xmin"),
  1321. (self.xmax, "Xmax"),
  1322. (self.ymin, "Ymin"),
  1323. (self.ymax, "Ymax"),
  1324. (self.area, "Area"),
  1325. (self.perimeter, "Perimeter"),
  1326. (self.major, "MajorAxis"),
  1327. (self.minor, "MinorAxis"),
  1328. (self.angle, "Angle"),
  1329. (self.woundDistance, "DistanceToWound"),
  1330. (self.woundAngle, "AngleToWound"),
  1331. )
  1332. self.excel_stats, self.excel_stat_names = map(list, zip(*excel_stats_table))
  1333. self.csv_stats = self.excel_stats + [self.edgeBrightnessValues]
  1334. self.csv_stat_names = self.excel_stat_names + [self.edge_brightness_name]
  1335. self.all_stats = self.csv_stats + [self.neighbors]
  1336. self.all_stat_names = self.csv_stat_names + [self.neighbor_py_name]
  1337. def reset_stats(self):
  1338. """Initialize all the stats to empty lists"""
  1339. for stat in self.all_stats:
  1340. stat[:] = []
  1341. def GetCentroids(self, index, doUpdate=True): # Returns a floating point value...
  1342. if doUpdate:
  1343. self.UpdateValuesList(index)
  1344. cmList = []
  1345. for v in self.valList:
  1346. cmList.append(center_of_mass((self.watershed[index] == v).astype(np.float)))
  1347. return cmList
  1348. def GetTripleJunctionImage(self, index):
  1349. "GetTripleJunctionImage generates a boolean array True at all points\n"
  1350. "where 4 pixels meet with at least 3 distinct values."
  1351. sh = [i - 1 for i in self.watershed[index].shape]
  1352. tripleJ = np.zeros(sh, dtype=np.bool)
  1353. for i in range(sh[0]):
  1354. for j in range(sh[1]):
  1355. if (
  1356. len(
  1357. set(
  1358. [
  1359. self.watershed[index][i][j],
  1360. self.watershed[index][i + 1][j],
  1361. self.watershed[index][i][j + 1],
  1362. self.watershed[index][i + 1][j + 1],
  1363. ]
  1364. )
  1365. )
  1366. > 2
  1367. ):
  1368. tripleJ[i][j] = 1
  1369. return tripleJ
  1370. def GetTripleJunctionPointsAndVals(self, index):
  1371. tj = np.array(np.where(self.GetTripleJunctionImage(index))).T
  1372. idsByTJ = []
  1373. idsByTJArr = np.zeros([len(tj), 4], np.int)
  1374. for t in range(len(tj)):
  1375. idsByTJ.append(
  1376. list(
  1377. set(
  1378. [
  1379. self.watershed[index][tj[t][0] + x][tj[t][1] + y]
  1380. for x, y in [[0, 0], [1, 0], [0, 1], [1, 1]]
  1381. ]
  1382. )
  1383. )
  1384. )
  1385. idsByTJArr[t][: len(idsByTJ[-1])] = idsByTJ[-1]
  1386. self.UpdateValuesList(index=index)
  1387. idList = self.valList
  1388. tjsByID = []
  1389. for v in idList:
  1390. tjsByID.append(np.where(idsByTJArr == v)[0].tolist())
  1391. # List of XY Coords of Triple Junctions
  1392. # List of all existing cellID's in this frame
  1393. # For each triple junction, the 3 or 4 cellID's touch it
  1394. # For each cell (by ID) which triple junctions touch it
  1395. return tj, idList, idsByTJ, tjsByID # These last 2 index into each other...
  1396. # If I ever decided to speed this up, the best way would be to pre-calculate all the pair distances
  1397. # and then find the shortest combined path from summing these up
  1398. # And I'm also sure someone has an algorithm that does it even better...
  1399. def GetPolygonsAndPerimeters(self):
  1400. lastActiveFrame = self.GetLastActiveFrame()
  1401. tjPolys = []
  1402. polyPerimeters = []
  1403. edgeCells = []
  1404. for index in range(lastActiveFrame + 1):
  1405. tj, idList, idsByTJ, tjsByID = self.GetTripleJunctionPointsAndVals(
  1406. index
  1407. ) # Runs an Update
  1408. tjPolys.append([None] * len(self.valList))
  1409. polyPerimeters.append([None] * len(self.valList))
  1410. for i, v in enumerate(self.valList): # same as idList
  1411. tjs = [tj[j] for j in tjsByID[i]]
  1412. tjPolys[-1][i] = GetMinPoly(tjs)
  1413. polyPerimeters[-1][i] = CalcPolyPerimeter(tjPolys[-1][i])
  1414. # so these, as with area, perimeter, etc, come out indexed by value *index*, not by value
  1415. # use tjPolys[i], NOT tjPolys[v]
  1416. return tjPolys, polyPerimeters
  1417. def GetBoundingRectangles(self, doUpdate=True):
  1418. if doUpdate:
  1419. self.UpdateValuesList()
  1420. boundsList = []
  1421. for v in self.valList:
  1422. x, y = np.where(self.watershed[self.index] == v)
  1423. boundsList.append([[min(x), max(x)], [min(y), max(y)]])
  1424. return boundsList
  1425. def CalculateArea(self, doUpdate=True):
  1426. if doUpdate:
  1427. self.UpdateValuesList()
  1428. # boundsList = self.GetBoundingRectangles() # not really used...
  1429. areaList = []
  1430. for v in self.valList:
  1431. areaList.append(np.sum(self.watershed[self.index] == v))
  1432. return areaList
  1433. def CalculatePerimeter(self, boundsList=None):
  1434. if boundsList is None:
  1435. self.UpdateValuesList()
  1436. boundsList = self.GetBoundingRectangles()
  1437. perimeterList = []
  1438. for i, v in enumerate(self.valList):
  1439. perimeterList.append(
  1440. ImageContour.GetIJPerimeter(
  1441. self.watershed[self.index], v, boundsList[i]
  1442. )
  1443. )
  1444. return perimeterList
  1445. def CalculateBestFitEllipse(self, boundsList=None):
  1446. if boundsList is None:
  1447. self.UpdateValuesList()
  1448. boundsList = self.GetBoundingRectangles()
  1449. ellipseList = []
  1450. for i, v in enumerate(self.valList):
  1451. [[xm, xM], [ym, yM]] = boundsList[i]
  1452. ellipseList.append(
  1453. EllipseFitter.EllipseFitter(
  1454. self.watershed[self.index][xm : xM + 1, ym : yM + 1] == v
  1455. )
  1456. )
  1457. return ellipseList
  1458. def CalculateCentroids(self, doUpdate=True):
  1459. cmList = self.GetCentroids(self.index, doUpdate=doUpdate)
  1460. return cmList
  1461. def CalculateWoundDistance(self, cmList=None):
  1462. # possibly open a user window to select wound location???
  1463. # should run from the SegmenterFrame and not WatershedData,though...
  1464. if cmList is None:
  1465. cmList = self.GetCentroids(self.index)
  1466. # Need to flip these
  1467. woundY, woundX = self.woundCenters[self.index]
  1468. rList = []
  1469. thetaList = []
  1470. for [x, y] in cmList:
  1471. r = np.sqrt((x - woundX) ** 2 + (y - woundY) ** 2)
  1472. theta = np.arctan2(1.0 * (x - woundX), 1.0 * (y - woundY))
  1473. rList.append(r)
  1474. thetaList.append(theta)
  1475. return rList, thetaList
  1476. def CalculateNeighbors(self, boundsList=None):
  1477. if boundsList is None:
  1478. self.UpdateValuesList()
  1479. boundsList = self.GetBoundingRectangles()
  1480. neighborList = []
  1481. for i, v in enumerate(self.valList):
  1482. neighborList.append([])
  1483. [[xm, xM], [ym, yM]] = boundsList[i]
  1484. if (
  1485. xm == 0
  1486. or ym == 0
  1487. or xM == self.watershed.shape[1] - 1
  1488. or yM == self.watershed.shape[2] - 1
  1489. ):
  1490. # cell is on the boundary
  1491. # this should never happen, because background is forced onto all of the edges
  1492. neighborList[i].append(0)
  1493. waterSmaller = self.watershed[self.index][xm - 1 : xM + 2, ym - 1 : yM + 2]
  1494. binary = waterSmaller == v
  1495. neighborPixels = (
  1496. scipy.ndimage.binary_dilation(binary) ^ binary
  1497. ) * waterSmaller
  1498. # Loop over neighborPixels just like in UpdateValuesList, but also look at background
  1499. for neighborVal in range(1, neighborPixels.max() + 1):
  1500. if np.any(neighborPixels == neighborVal):
  1501. neighborList[i].append(neighborVal)
  1502. return neighborList
  1503. def CalculateEdgeBrightnessValues(self, thickness=1, saveDir=None):
  1504. mask = CreateThickOutlines(self.watershed[self.index], thickness)
  1505. if saveDir:
  1506. outlDir = os.path.join(
  1507. saveDir, "ThickOutlines" + (str(thickness) if thickness > 1 else "")
  1508. )
  1509. if not os.path.exists(outlDir):
  1510. os.mkdir(outlDir)
  1511. GTL.SaveSingle(
  1512. 255 * mask, os.path.join(outlDir, "Mask%i" % self.index), tiffBits=8
  1513. )
  1514. edgeBrightnessValues = sorted(
  1515. self.origData[self.index][np.where(mask)].tolist()
  1516. )
  1517. return edgeBrightnessValues
  1518. def GetMaxSeedVal(self):
  1519. maxSeedVal = 2
  1520. for l in self.sparseList:
  1521. if l is not None:
  1522. if l.nnz != 0:
  1523. maxSeedVal = max(maxSeedVal, l.tocoo().data.max())
  1524. return maxSeedVal
  1525. def CollectAllStats(self, saveDirForOutlineMasks=None):
  1526. maxSeedVal = self.GetMaxSeedVal()
  1527. self.reset_stats()
  1528. oldIndex = self.index
  1529. adjLength = self.GetLastActiveFrame() + 1
  1530. for index in range(adjLength):
  1531. if self.watershed[index].max() < 2:
  1532. print("frame", index, "has only background seeds")
  1533. break
  1534. self.index = index
  1535. print("Calculating for Frame", index)
  1536. print("Get Values")
  1537. self.UpdateValuesList()
  1538. self.valuesByFrame.append(self.valList)
  1539. print("Get Centroids")
  1540. centroids = self.CalculateCentroids(doUpdate=False)
  1541. centroidX, centroidY = zip(*centroids)
  1542. self.centroidX.append(list(centroidX))
  1543. self.centroidY.append(list(centroidY))
  1544. print("Get Bounds")
  1545. boundsList = self.GetBoundingRectangles(doUpdate=False)
  1546. br = np.array(boundsList)
  1547. self.xmin.append(br[:, 0, 0].tolist())
  1548. self.xmax.append(br[:, 0, 1].tolist())
  1549. self.ymin.append(br[:, 1, 0].tolist())
  1550. self.ymax.append(br[:, 1, 1].tolist())
  1551. print("Get Area")
  1552. self.area.append(self.CalculateArea(doUpdate=False))
  1553. print("Get Perimeter")
  1554. self.perimeter.append(self.CalculatePerimeter(boundsList=boundsList))
  1555. print("Get BF Ellipse")
  1556. bfe = np.array(self.CalculateBestFitEllipse(boundsList=boundsList))
  1557. self.major.append(bfe[:, 1].tolist())
  1558. self.minor.append(bfe[:, 2].tolist())
  1559. self.angle.append(bfe[:, 0].tolist())
  1560. rList, thetaList = self.CalculateWoundDistance(cmList=centroids)
  1561. self.woundDistance.append(rList)
  1562. self.woundAngle.append(thetaList)
  1563. print("Get Neighbors")
  1564. self.neighbors.append(self.CalculateNeighbors(boundsList=boundsList))
  1565. print("Get Edge Brightness Values and Save Edge Mask")
  1566. self.edgeBrightnessValues.append(
  1567. self.CalculateEdgeBrightnessValues(saveDir=saveDirForOutlineMasks)
  1568. )
  1569. print("Fix Offsets")
  1570. # Make it so that there is an entry for every value, regardless of whether that cell was
  1571. # in that frame or not
  1572. # This makes all the quantities line up...
  1573. for v in range(
  1574. 2, maxSeedVal + 1
  1575. ): # This can get ugly if there is too much deleting and what-not...
  1576. # Use the CompressSeedVals Button to remedy the situation...
  1577. for i in range(len(self.valuesByFrame)):
  1578. if not v in self.valuesByFrame[i]:
  1579. for m in self.all_stats[
  1580. 1:
  1581. ]: # skip the first stat, self.valuesByFrame... this made this measure totally worthless and confusing
  1582. m[i].insert(v - 2, None)
  1583. print("Done Collecting!")
  1584. self.index = oldIndex
  1585. def SaveAllStats(self, directory=None):
  1586. if directory is None:
  1587. directory = wx.DirSelector("Choose which directory to save the stats")
  1588. if not all(self.all_stats):
  1589. for n, m in zip(self.all_stat_names, self.all_stats):
  1590. print(n)
  1591. print(m)
  1592. print("")
  1593. s = os.linesep.join(
  1594. [
  1595. "Skip saving stats because some stats are missing!",
  1596. "To save the stats, do RunCalculations first",
  1597. ]
  1598. )
  1599. print(s)
  1600. wx.MessageBox(s)
  1601. return
  1602. data, names = self.excel_stats, self.excel_stat_names
  1603. # Fix to replace numpy types with Python types:
  1604. for i in range(len(data)):
  1605. data[i] = [
  1606. [(d.item() if hasattr(d, "item") else d) for d in dat]
  1607. for dat in data[i]
  1608. ]
  1609. adjLength = self.GetLastActiveFrame() + 1
  1610. dataSets, skip_val, numBreakups = (
  1611. _data_splitter_for_excel(data, adjLength)
  1612. if adjLength > 256
  1613. else ([data], 1, 1)
  1614. )
  1615. for i, d in enumerate(dataSets): # write a file for each set, max 256 frames...
  1616. cutByStr = "" if skip_val == 1 else "CutBy" + str(skip_val)
  1617. partStr = "" if numBreakups == 1 else "Part" + str(i + 1)
  1618. f = os.path.join(directory, "Calculations" + cutByStr + partStr + ".xls")
  1619. ExcelHelper.excelWrite(d, names, f, flip=True)
  1620. for d, name in zip(data, names):
  1621. SaveCSV(name, directory, d)
  1622. # This won't excel-ify very well since it's got too many columns
  1623. SaveCSV(self.edge_brightness_name, directory, self.edgeBrightnessValues)
  1624. # This won't excel-ify very well since it's a 3D data set with variable numbers of links...
  1625. # So just save it as a .py file instead...
  1626. with open(os.path.join(directory, self.neighbor_py_name), "w") as fid:
  1627. fid.write("neighbors = " + repr(self.neighbors))
  1628. def CheckForMalformedRegions(self):
  1629. """This goes through all frames and values and uses shapely to test if regions are disjoint in any way"""
  1630. ### Deprecated... just use the one in SWHelpers directly!
  1631. SWHelpers.CheckForMalformedRegions(self.wd.watershed, usePrint=True)
  1632. def SaveTripleJunctions(self, directory=None):
  1633. """This is horribly slow, so keep it out of RunCalc and RunCalc2!"""
  1634. if directory is None:
  1635. directory = wx.DirSelector("Choose which directory to save the stats")
  1636. adjLength = self.GetLastActiveFrame() + 1
  1637. fid = open(os.path.join(directory, "TripleJunctions.py"), "w")
  1638. fid.write("[")
  1639. for i in range(adjLength):
  1640. tj = np.array(np.where(self.GetTripleJunctionImage(i))).T.tolist()
  1641. fid.write(repr(tj))
  1642. if i != adjLength - 1:
  1643. fid.write(", ")
  1644. fid.write("]")
  1645. fid.close()
  1646. def GetActiveSeedValues(self):
  1647. return sorted(list(set(self.sparseList[self.index].tocoo().data.tolist())))
  1648. def SaveTripleJunctionsWithCellIDs(self, directory=None):
  1649. """This is horribly slow, so keep it out of RunCalc and RunCalc2!"""
  1650. if directory is None:
  1651. directory = wx.DirSelector("Choose which directory to save the stats")
  1652. adjLength = self.GetLastActiveFrame() + 1
  1653. bigData = []
  1654. ColNames = ["X", "Y", "Cell ID 1", "Cell ID 2", "Cell ID 3", "(Cell ID 4)"]
  1655. for i in range(adjLength):
  1656. tj = np.where(self.GetTripleJunctionImage(i))
  1657. l = []
  1658. for t in range(len(tj[0])):
  1659. cellIDs = list(
  1660. set(
  1661. [
  1662. self.watershed[i][tj[0][t] + x][tj[1][t] + y]
  1663. for x, y in [[0, 0], [1, 0], [0, 1], [1, 1]]
  1664. ]
  1665. )
  1666. )
  1667. cellIDs = list(map(str, cellIDs))
  1668. if len(cellIDs) == 3:
  1669. cellIDs = cellIDs + [""]
  1670. l.append([str(tj[0][t]), str(tj[1][t])] + cellIDs)
  1671. bigData.append([ColNames] + l)
  1672. ExcelHelper.excelWrite(
  1673. bigData,
  1674. [str(i + 1) for i in range(adjLength)],
  1675. os.path.join(directory, "TripleJunctionsWithCellIDs.xls"),
  1676. )
  1677. # NOT TESTED
  1678. def GetNeighborPairsByFrame(
  1679. self, woundVals
  1680. ): # Merges wound vals... make sure you run LoadStats or CollectAllStats first!
  1681. neighborPairsByFrame = []
  1682. for frame in range(self.length):
  1683. MergeWoundVals(self.neighbors[frame], woundVals)
  1684. neighborPairs = set()
  1685. for i in range(len(self.neighbors[frame])):
  1686. if self.neighbors[frame][i] is not None:
  1687. if len(self.neighbors[frame][i]) > 0:
  1688. neighborPairs.update(
  1689. [
  1690. tuple(sorted([i + 2, j]))
  1691. for j in self.neighbors[frame][i]
  1692. ]
  1693. )
  1694. neighborPairsByFrame.append(sorted(list(neighborPairs)))
  1695. return neighborPairsByFrame
  1696. # NOT TESTED
  1697. def GetContourValuesLengthsAndSubContoursByFrame(
  1698. self, allValsByFrame, woundValsSorted
  1699. ):
  1700. ######### NOT DONE!!!!!!! ####################
  1701. # THIS IS NOT THIS SIMPLE... STILL NEED TO SOMEHOW MERGE WOUND
  1702. cVLSByFrame = []
  1703. for frame in range(self.length):
  1704. cVLS = [] # for each subcountour: [value, length, subcontour points]
  1705. for v in allValsByFrame[frame]:
  1706. wi = self.watershed[frame]
  1707. for v in woundValsSorted[1:]:
  1708. watershed[np.where(watershed == v)] = woundValsSorted[0]
  1709. boundingRect = ImageContour.GetBoundingRect(wi, v)
  1710. # No longer needed: #contour,turns,vals = ImageContour.GetContour(wi,v,boundingRect=boundingRect,byNeighbor=True)
  1711. perimeterVals, perimeterList, subContours = ImageContour.GetPerimeterByNeighborVal(
  1712. wi, v, boundingRect=boundingRect, getSubContours=True
  1713. )
  1714. subContoursAdj = [
  1715. (np.array(sc) + [boundingRect[0][0], boundingRect[1][0]]).tolist()
  1716. for sc in subContours
  1717. ] # Will need to - 0.5 to line up on an overlay
  1718. if len(perimeterList) > 0:
  1719. cVLS += [
  1720. [
  1721. sorted([v, perimeterVals[i]]),
  1722. perimeterList[i],
  1723. subContoursAdj[i],
  1724. ]
  1725. for i in range(len(perimeterVals))
  1726. ]
  1727. cVLS.sort(key=lambda x: x[0])
  1728. for i in range(len(cVLS) - 1, 0, -1):
  1729. if cVLS[i - 1][0] == cVLS[i][0]: # if 2 subcoutours are the same,
  1730. cVLS[i - 1][1] = min(
  1731. cVLS[i - 1][1], cVLS[i][1]
  1732. ) # keep only the one with the minimum length computation
  1733. del cVLS[i]
  1734. cVLSByFrame.append(cVLS)
  1735. return cVLSByFrame
  1736. # NOT TESTED
  1737. def SaveSubContours(self, directory=None):
  1738. """This is horribly slow, so keep it out of RunCalc and RunCalc2!"""
  1739. if directory is None:
  1740. directory = wx.DirSelector("Choose which directory to save the stats")
  1741. MI = self.GetManualInputs(d)
  1742. if MI is None:
  1743. return
  1744. woundValsSorted = sorted(MI.woundVals)
  1745. self.CollectAllStats(directory) # Safer but slower way to do it...
  1746. neighborPairsByFrame = self.GetNeighborPairsByFrame(woundValsSorted)
  1747. allValsByFrame = GetAllValsByFrame(neighborPairsByFrame)
  1748. cVLSByFrame = self.GetContourValuesLengthsAndSubContoursByFrame(
  1749. allValsByFrame, woundValsSorted
  1750. )
  1751. fid = open(os.path.join(directory, "CellBoundarySubContours.py"), "w")
  1752. fid.write("cVLSByFrame = " + repr(cVLSByFrame))
  1753. fid.close()
  1754. def LoadStats(self, directory=None):
  1755. if directory is None:
  1756. directory = wx.DirSelector("Choose which directory to save the stats")
  1757. # Initialise the variables...
  1758. self.reset_stats()
  1759. for stat, name in zip(self.csv_stats, self.csv_stat_names):
  1760. nameCsv = os.path.join(directory, name + ".csv")
  1761. if os.path.exists(nameCsv):
  1762. with open(nameCsv, "r") as fid:
  1763. s = fid.read()
  1764. # Remove all the '\r' first
  1765. s = s.replace("\r\n", "\n").replace("\n\n", "\n")
  1766. s = (
  1767. s.replace("(", "")
  1768. .replace(")", "")
  1769. .replace("[", "")
  1770. .replace("]", "")
  1771. .replace("L,", ",")
  1772. ) # bugfix for some csv files...
  1773. s = s[:-1] if s[-1] == "L" else s
  1774. if "\r" in s: # Fail for unknown/unusual lineseps
  1775. print(
  1776. "File must have either \\r\\n (Windows) or \\n (Unix) file endings!"
  1777. )
  1778. continue
  1779. lines = s.split("\n")
  1780. dprint(name)
  1781. int_fields = [
  1782. "CellValuesByFrame",
  1783. "Xmin",
  1784. "Xmax",
  1785. "Ymin",
  1786. "Ymax",
  1787. "EdgeBrightnessValues",
  1788. ]
  1789. conv = IorN if name in int_fields else ForN
  1790. stat[:] = [
  1791. list(map(conv, line.replace(" ", "").split(","))) for line in lines
  1792. ]
  1793. else:
  1794. print("No file named", nameCsv)
  1795. print("Will not load", name)
  1796. neighborsPy = os.path.join(directory, self.neighbor_py_name)
  1797. if os.path.exists(neighborsPy):
  1798. with open(neighborsPy, "U") as fid:
  1799. Neighbors = imp.load_module(
  1800. "Neighbors", fid, neighborsPy, (".py", "U", 1)
  1801. )
  1802. self.neighbors[:] = Neighbors.neighbors
  1803. def CreateBinsWDistance(self, frameToCompare, woundVals, binSize=30, initR=0):
  1804. if not self.woundDistance:
  1805. print(
  1806. "Error! Need have collected stats for the data before running CreateBinsWDistance!"
  1807. )
  1808. return
  1809. d = np.array(self.woundDistance)
  1810. # Exchange all the None's for NaN's in the wound distances...
  1811. dB = np.array(d)
  1812. for i in range(dB.shape[0]):
  1813. for j in range(dB.shape[1]):
  1814. if dB[i, j] is None:
  1815. dB[i, j] = np.nan
  1816. # Sort the wound distances based on the distances at a particular time
  1817. dBm = np.mean(dB, 0)
  1818. d_comp = d[frameToCompare] # pick a frame to use in sorting
  1819. l = list(range(len(dBm)))
  1820. l.sort(key=lambda x: d_comp[x])
  1821. # create bins of equal size...
  1822. # bins will each contain an index list
  1823. nbins = int(np.ceil((d_comp.max() - initR) / binSize))
  1824. bins = [[] for i in range(nbins)]
  1825. for i in range(len(d_comp)):
  1826. if d_comp[i] is not None and i + 2 not in woundVals: # The wound...
  1827. ind = int(np.floor((d_comp[i] - initR) / binSize))
  1828. bins[ind].append(i)
  1829. while bins[-1] == []:
  1830. del bins[-1]
  1831. return bins
  1832. def CreateBinsWNeighbors(self, frameToCompare, woundVals):
  1833. if not self.neighbors:
  1834. print(
  1835. "Error! Need have collected stats for the data before running CreateBinsWNeighbors!"
  1836. )
  1837. return
  1838. nei = self.neighbors[frameToCompare]
  1839. bins = []
  1840. wm2 = [w - 2 for w in woundVals]
  1841. dprint(wm2)
  1842. while True:
  1843. bins.append([])
  1844. i = wm2 if len(bins) < 2 else bins[-2]
  1845. dprint(i)
  1846. for w in i:
  1847. dprint(nei[w])
  1848. if nei[w] is not None:
  1849. for v in nei[w]:
  1850. if v > 1: # skip background
  1851. bins[-1].append(v - 2)
  1852. bins[-1] = list(
  1853. set(bins[-1]).difference(*([wm2] + list(map(set, bins[:-1]))))
  1854. ) # remove duplicates...
  1855. if bins[-1] == []:
  1856. break
  1857. while bins[-1] == []:
  1858. del bins[-1]
  1859. return bins
  1860. def GetWoundNeighborContourLengths(
  1861. self, index, woundVals, printPerimeterCheck=False
  1862. ):
  1863. """Find the boundary lengths of the wound and it's neighboring cells"""
  1864. # Warning! No index checking...
  1865. newWVal = 1e6 # I don't think anyone will ever create a million cells...
  1866. wi = np.array(self.watershed[index], dtype=np.int)
  1867. for v in woundVals:
  1868. wi[np.where(wi == v)] = newWVal
  1869. # get contour lengths and contour length values for the wound-neighbor interfaces...
  1870. WNv, WNl = ImageContour.GetPerimeterByNeighborVal(wi, newWVal)
  1871. if printPerimeterCheck:
  1872. totalPer = ImageContour.GetIJPerimeter(wi, newWVal)
  1873. print("sum of contours", sumN(WNl))
  1874. print("total perimeter", totalPer)
  1875. NWl = []
  1876. NWv = []
  1877. NNl = []
  1878. NNv = []
  1879. NOl = []
  1880. NOv = []
  1881. for val in WNv:
  1882. # get contour lengths and contour length values for the wound's neighbor-neighbor interfaces...
  1883. clv, cl = ImageContour.GetPerimeterByNeighborVal(wi, val)
  1884. for i in range(len(clv)):
  1885. if clv[i] == newWVal:
  1886. NWl.append(cl[i])
  1887. NWv.append(val)
  1888. elif clv[i] in WNv:
  1889. NNl.append(cl[i])
  1890. NNv.append([val, clv[i]])
  1891. else:
  1892. NOl.append(cl[i])
  1893. NOv.append([val, clv[i]])
  1894. if printPerimeterCheck:
  1895. print("sum of contours (B)", sumN(NWl))
  1896. print("sum of contours Out", sumN(NOl))
  1897. for i in range(len(WNv)):
  1898. # just for good measure ;)
  1899. if WNv != NWv: # WHAT DOES THIS REALLY DO???
  1900. print(
  1901. "Error!!! Wound-Neighbor contours do not match Neighbor-Wound contours!"
  1902. )
  1903. break
  1904. WNl[i] = min(WNl[i], NWl[i])
  1905. NWl = WNl # Just for completeness...
  1906. if printPerimeterCheck:
  1907. print("sum of countours (C)", sumN(WNl))
  1908. # So, WNv and NWv are equal
  1909. # and I should average together WNl and NWl to get the best answer for the contour length...
  1910. for i in range(len(NNv)):
  1911. for j in range(len(NNv)):
  1912. if i >= len(NNv) or j >= len(NNv):
  1913. break
  1914. if NNv[i] == NNv[j][::-1]:
  1915. NNl[i] = min(NNl[i], NNl[j])
  1916. del NNv[j]
  1917. del NNl[j]
  1918. return WNv, WNl, NNv, NNl, NOv, NOl
  1919. def GetWNOContourLengthsSorted(self, woundVals):
  1920. WNv, WNl, NNv, NNl, NOv, NOl = [], [], [], [], [], []
  1921. WNsortedVals, NNsortedVals, NOsortedVals = [], [], []
  1922. for index in range(self.length):
  1923. dprint("index: " + str(index))
  1924. if self.sparseList[index] is not None:
  1925. if self.sparseList[index].nnz != 0:
  1926. for i in [WNv, WNl, NNv, NNl, NOv, NOl]:
  1927. i.append([])
  1928. WNv[-1], WNl[-1], NNv[-1], NNl[-1], NOv[-1], NOl[
  1929. -1
  1930. ] = self.GetWoundNeighborContourLengths(index, woundVals)
  1931. for i in WNv[-1]:
  1932. if i not in WNsortedVals:
  1933. WNsortedVals.append(i)
  1934. for i in NNv[-1]:
  1935. if (i not in NNsortedVals) and (i[::-1] not in NNsortedVals):
  1936. NNsortedVals.append(i)
  1937. for i in NOv[-1]:
  1938. if i not in NOsortedVals:
  1939. NOsortedVals.append(i)
  1940. WNsortedVals.sort()
  1941. NNsortedVals.sort()
  1942. NOsortedVals.sort()
  1943. WNl_byVal = []
  1944. for v in WNsortedVals:
  1945. WNl_byVal.append([])
  1946. for frame in range(len(WNv)):
  1947. if v in WNv[frame]:
  1948. ind = WNv[frame].index(v)
  1949. WNl_byVal[-1].append(WNl[frame][ind])
  1950. else:
  1951. WNl_byVal[-1].append(None)
  1952. NNl_byVal = []
  1953. for v in NNsortedVals:
  1954. NNl_byVal.append([])
  1955. for frame in range(len(NNv)):
  1956. forw = v in NNv[frame]
  1957. back = v[::-1] in NNv[frame]
  1958. if forw and back:
  1959. print("Both shouldn't be in the list...")
  1960. break
  1961. if forw:
  1962. ind = NNv[frame].index(v)
  1963. NNl_byVal[-1].append(NNl[frame][ind])
  1964. if back:
  1965. ind = NNv[frame].index(v[::-1])
  1966. NNl_byVal[-1].append(NNl[frame][ind])
  1967. if not forw and not back:
  1968. NNl_byVal[-1].append(None)
  1969. NOl_byVal = []
  1970. for v in NOsortedVals:
  1971. NOl_byVal.append([])
  1972. for frame in range(len(NOv)):
  1973. if v in NOv[frame]:
  1974. ind = NOv[frame].index(v)
  1975. NOl_byVal[-1].append(NOl[frame][ind])
  1976. else:
  1977. NOl_byVal[-1].append(None)
  1978. return (
  1979. WNv,
  1980. WNl,
  1981. NNv,
  1982. NNl,
  1983. NOv,
  1984. NOl,
  1985. WNsortedVals,
  1986. WNl_byVal,
  1987. NNsortedVals,
  1988. NNl_byVal,
  1989. NOsortedVals,
  1990. NOl_byVal,
  1991. )
  1992. def GetBinnedContours(self, woundVals, WNv, WNl, NNl, NOl, usePrint=True):
  1993. """Return contours for Wound Perimeter (3 versions)""" + """ Length of Tangential Boundaries from Wound""" + """ Total Perimeter Around First Ring Of Cells Around Wound (2 versions)"""
  1994. WP, WPc2, spokes, WNP, WNPc = [], [], [], [], []
  1995. # The easy way to calculate these:
  1996. WPc = [sumN(i) for i in WNl]
  1997. spokes = [sumN(i) for i in NNl]
  1998. WNPc = [sumN(i) for i in NOl]
  1999. # Verification ;)
  2000. for frame in range(self.length):
  2001. if self.sparseList[frame] is None:
  2002. break
  2003. elif self.sparseList[frame].nnz == 0:
  2004. break
  2005. # get the wound perimeter the good way ;)
  2006. newWVal = 1e6 # I don't think anyone will ever create a million cells...
  2007. wi = np.array(self.watershed[frame], dtype=np.int)
  2008. for v in woundVals:
  2009. wi[np.where(wi == v)] = newWVal
  2010. WP.append(ImageContour.GetIJPerimeter(wi, newWVal))
  2011. WPc2.append(sumN(ImageContour.GetPerimeterByNeighborVal(wi, newWVal)[1]))
  2012. # spokes.append(sumN(NNl[frame]))
  2013. # Total P of all NNs
  2014. totalWN_Per = sumN(
  2015. [self.perimeter[frame][v - 2] for v in WNv[frame]]
  2016. ) # I think for this we really need the local vals for each frame...
  2017. # Perimeter of second ring around wound
  2018. WNP.append(totalWN_Per - sumN(WNl[frame]) - 2 * sumN(NNl[frame]))
  2019. # WNPc.append(sumN(NOl[frame]))
  2020. if usePrint:
  2021. print("Wound Perimeters")
  2022. print(WP)
  2023. print("Wound Perimeter by Contours")
  2024. print(WPc)
  2025. print("Wound Perimeter by Contours (B version)")
  2026. print(WPc2)
  2027. print("Spokes")
  2028. print(spokes)
  2029. print("Perimeter of Second Ring Around Wound")
  2030. print(WNP)
  2031. print("Perimeter of Second Ring Around Wound (B)")
  2032. print(WNPc)
  2033. return WP, WPc, WPc2, spokes, WNP, WNPc
  2034. # Later, change this to use ExendedWoundContours instead of wcList,swcList
  2035. def PerimeterTests(
  2036. self, frame, testInd, woundVals, wcList, swcList, useSorted=False
  2037. ):
  2038. """perimeter check for NO"""
  2039. [WNv, WNl, NNv, NNl, NOv, NOl] = wcList
  2040. [
  2041. WNsortedVals,
  2042. WNl_byVal,
  2043. NNsortedVals,
  2044. NNl_byVal,
  2045. NOsortedVals,
  2046. NOl_byVal,
  2047. ] = swcList
  2048. if not self.perimeter:
  2049. print(
  2050. "Error! Must collect stats for the data before running PerimeterTests!"
  2051. )
  2052. return
  2053. # get the wound perimeter the good way ;)
  2054. newWVal = 1e6 # I don't think anyone will ever create a million cells...
  2055. wi = np.array(self.watershed[frame], dtype=np.int)
  2056. for v in woundVals:
  2057. wi[np.where(wi == v)] = newWVal
  2058. WP = ImageContour.GetIJPerimeter(wi, newWVal)
  2059. WPc = sumN(ImageContour.GetPerimeterByNeighborVal(wi, newWVal)[1])
  2060. print("Wound Perimeter")
  2061. print(WP)
  2062. print("Wound Perimeter by Contours")
  2063. print(WPc)
  2064. print("total P of all NNs")
  2065. print(sumN([self.perimeter[frame][v - 2] for v in WNv[frame]]))
  2066. print("sum of WNlengths, NN lengths and NO lengths")
  2067. if useSorted:
  2068. print(
  2069. sumN(np.array(WNl_byVal)[:, frame])
  2070. + 2 * sumN(np.array(NNl_byVal)[:, frame])
  2071. + sumN(np.array(NOl_byVal)[:, frame])
  2072. )
  2073. else:
  2074. print(sumN(WNl[frame]) + 2 * sumN(NNl[frame]) + sumN(NOl[frame]))
  2075. print("Num of WN vals:")
  2076. if useSorted:
  2077. print(len(WNsortedVals))
  2078. else:
  2079. print(len(WNv[frame]))
  2080. if useSorted:
  2081. testVal = WNsortedVals[testInd]
  2082. else:
  2083. testVal = WNv[frame][testInd]
  2084. print("Total P for", testVal, "w/GP")
  2085. print(self.perimeter[frame][testVal - 2])
  2086. vals, pers = ImageContour.GetPerimeterByNeighborVal(
  2087. self.watershed[frame], testVal
  2088. )
  2089. print("Total P for", testVal, "w/GPBN")
  2090. print(sumN(pers))
  2091. print("WN part")
  2092. if useSorted:
  2093. s1 = WNl_byVal[testInd][frame]
  2094. else:
  2095. s1 = WNl[frame][testInd]
  2096. print(s1)
  2097. if s1 is None:
  2098. print("This value (", testVal, ") is not a wound neighbor on this frame!")
  2099. else:
  2100. s2 = 0
  2101. if useSorted:
  2102. for i, vL in enumerate(NNsortedVals):
  2103. if testVal in vL:
  2104. if NNl_byVal[i][frame] is not None:
  2105. s2 += NNl_byVal[i][frame]
  2106. else:
  2107. for i, vL in enumerate(NNv[frame]):
  2108. if testVal in vL:
  2109. if NNl[frame][i] is not None:
  2110. s2 += NNl[frame][i]
  2111. print("NN part")
  2112. print(s2)
  2113. s3 = 0
  2114. if useSorted:
  2115. for i, vL in enumerate(NOsortedVals):
  2116. if testVal in vL:
  2117. if NOl_byVal[i][frame] is not None:
  2118. s3 += NOl_byVal[i][frame]
  2119. else:
  2120. for i, vL in enumerate(NOv[frame]):
  2121. if testVal in vL:
  2122. if NOl[frame][i] is not None:
  2123. s3 += NOl[frame][i]
  2124. print("NO part")
  2125. print(s3)
  2126. print("Sum of last 3:")
  2127. print(s1 + s2 + s3)
  2128. def RunCalculations2(self, d):
  2129. if not all(self.all_stats):
  2130. print("You must do CollectAllStats before RunCalculations2!")
  2131. return
  2132. MI = self.GetManualInputs(d)
  2133. if MI is None:
  2134. return
  2135. woundVals = MI.woundVals
  2136. if woundVals == []:
  2137. print("Error! You need to define the woundVals in ManualInputs.py!")
  2138. return
  2139. if MI.timeIntervals == [] or MI.numberFramesPerSeries == []:
  2140. print("Error! You need to define the time information in ManualInputs.py!")
  2141. return
  2142. for i in range(self.length):
  2143. if self.sparseList[i] is None:
  2144. break
  2145. else:
  2146. hasWound = False
  2147. for wv in woundVals:
  2148. if wv in self.GetActiveSeedValues():
  2149. hasWound = True
  2150. break
  2151. if not hasWound:
  2152. print("Error! Frame " + str(i) + " does not have any wound!")
  2153. print(
  2154. "Error! You must have a wound region in all frames to use RunCalculations2!!!"
  2155. )
  2156. print("Option 1: If you messed up the wound values, correct them.")
  2157. print(
  2158. "Option 2: Go through each initialized frame and add at least a small bit of wound where it might be."
  2159. )
  2160. print(
  2161. " Then re-run RunCalculations and then try RunCalculations2."
  2162. )
  2163. return
  2164. # Perform Processing and output steps here:
  2165. ###############################
  2166. # BTW, just hard-code:
  2167. binSize = None
  2168. initR = None
  2169. useNei = True
  2170. timeAxis = CreateTimeAxis(
  2171. MI.timeIntervals,
  2172. MI.gapIntervals,
  2173. MI.numberFramesPerSeries,
  2174. len(self.centroidX),
  2175. )
  2176. BinningFrame = 3
  2177. if useNei:
  2178. bins = self.CreateBinsWNeighbors(BinningFrame, woundVals)
  2179. else:
  2180. bins = self.CreateBinsWDistance(
  2181. BinningFrame, woundVals, binSize=binSize, initR=initR
  2182. )
  2183. # etc...
  2184. # Added information about which cells are in each ring
  2185. # Save a file with the length of each ring and the values of the cells in each ring
  2186. fid = open(os.path.join(d, "cellsPerRing.csv"), "w")
  2187. fid.write("The number of cells in each ring by ring:\n")
  2188. for i in range(len(bins)):
  2189. fid.write(str(len(bins[i])) + ",")
  2190. fid.write("\n")
  2191. fid.write("The actual cell values for each ring (each column):\n")
  2192. # now write out each of the cell IDs...
  2193. for i in range(len(bins)):
  2194. fid.write(",".join([str(b + 2) for b in bins[i]]))
  2195. fid.write("\n")
  2196. fid.close()
  2197. # Hitting Error Here:
  2198. WNv, WNl, NNv, NNl, NOv, NOl, WNsortedVals, WNl_byVal, NNsortedVals, NNl_byVal, NOsortedVals, NOl_byVal = self.GetWNOContourLengthsSorted(
  2199. woundVals
  2200. )
  2201. WP, WPc, WPc2, spokes, WNP, WNPc = self.GetBinnedContours(
  2202. woundVals, WNv, WNl, NNl, NOl, usePrint=False
  2203. )
  2204. ExcelHelper.excelWrite(
  2205. [
  2206. [
  2207. ["Time Axis"] + timeAxis[: len(WP)],
  2208. ["Wound Perimeter"] + WP,
  2209. ["Wound Perimeter (Countour Method)"] + WPc,
  2210. ["Wound Perimeter (Countour Method 2)"] + WPc2,
  2211. ["Total Radial Contour Length"] + spokes,
  2212. ["Perimeter Around 1st Ring"] + WNP,
  2213. ["Perimeter Around 1st Ring (Method 2)"] + WNPc,
  2214. ]
  2215. ],
  2216. ["Contour Lengths"],
  2217. os.path.join(d, "ContourLengths.xls"),
  2218. flip=True,
  2219. )
  2220. areaBinned = GetBinnedValueForExcel(
  2221. self.area, "Area", bins, timeAxis, woundVals
  2222. )
  2223. # areaBinned -> [binNumber][frameNumber][ValueWithinBin]
  2224. # So, for i in areaBinned:
  2225. # Create an excel sheet with rows denote ValueWithinBin and columns denote time
  2226. ExcelHelper.excelWrite(
  2227. areaBinned,
  2228. sheetnames=["Summary", "Wound"]
  2229. + [
  2230. GetPlacementName(i - 1) + " Ring of Cells Around Wound"
  2231. for i in range(2, len(areaBinned))
  2232. ],
  2233. filename=os.path.join(d, "BinnedAreas.xls"),
  2234. )
  2235. # Output a binned file for major and minor, too...
  2236. majorBinned = GetBinnedValueForExcel(
  2237. self.major, "Minor Axis", bins, timeAxis, woundVals
  2238. )
  2239. minorBinned = GetBinnedValueForExcel(
  2240. self.minor, "Major Axis", bins, timeAxis, woundVals
  2241. )
  2242. ExcelHelper.excelWrite(
  2243. majorBinned,
  2244. sheetnames=["Summary", "Wound"]
  2245. + [
  2246. GetPlacementName(i - 1) + " Ring of Cells Around Wound"
  2247. for i in range(2, len(areaBinned))
  2248. ],
  2249. filename=os.path.join(d, "BinnedMajor.xls"),
  2250. )
  2251. ExcelHelper.excelWrite(
  2252. minorBinned,
  2253. sheetnames=["Summary", "Wound"]
  2254. + [
  2255. GetPlacementName(i - 1) + " Ring of Cells Around Wound"
  2256. for i in range(2, len(areaBinned))
  2257. ],
  2258. filename=os.path.join(d, "BinnedMinor.xls"),
  2259. )
  2260. # I'm going to refer to binned averages here (like m up above...)
  2261. # put each quantity in each sheet
  2262. majorMeans = [["time"] + ["ring" + str(i + 1) for i in range(len(bins))]]
  2263. minorMeans = [["time"] + ["ring" + str(i + 1) for i in range(len(bins))]]
  2264. aspectRatioMeans = [["time"] + ["ring" + str(i + 1) for i in range(len(bins))]]
  2265. angleMeans = [["time"] + ["ring" + str(i + 1) for i in range(len(bins))]]
  2266. ax_ththMeans = [["time"] + ["ring" + str(i + 1) for i in range(len(bins))]]
  2267. ax_rrMeans = [["time"] + ["ring" + str(i + 1) for i in range(len(bins))]]
  2268. rrdththMeans = [["time"] + ["ring" + str(i + 1) for i in range(len(bins))]]
  2269. for frame in range(self.length):
  2270. if self.sparseList[frame] is None:
  2271. break
  2272. elif self.sparseList[frame].nnz == 0:
  2273. break
  2274. if i >= len(self.woundCenters):
  2275. print("No Wound Centers After Frame", i, "!")
  2276. break
  2277. majorMeans.append([timeAxis[frame]])
  2278. minorMeans.append([timeAxis[frame]])
  2279. aspectRatioMeans.append([timeAxis[frame]])
  2280. angleMeans.append([timeAxis[frame]])
  2281. ax_ththMeans.append([timeAxis[frame]])
  2282. ax_rrMeans.append([timeAxis[frame]])
  2283. rrdththMeans.append([timeAxis[frame]])
  2284. for b in range(len(bins)):
  2285. woundAngles = [self.woundAngle[frame][i] for i in bins[b]]
  2286. cx, cy = self.woundCenters[frame]
  2287. majorBin = []
  2288. minorBin = []
  2289. aspectRatioBin = []
  2290. angleBin = []
  2291. ax_ththBin = []
  2292. ax_rrBin = []
  2293. rrdththBin = []
  2294. for i, wa in enumerate(woundAngles):
  2295. # print('Value',bins[b][i]+2)
  2296. majorBin.append(self.major[frame][bins[b][i]])
  2297. minorBin.append(self.minor[frame][bins[b][i]])
  2298. if None in (majorBin[-1], minorBin[-1]):
  2299. aspectRatioBin.append(None)
  2300. angleBin.append(None)
  2301. ax_ththBin.append(None)
  2302. ax_rrBin.append(None)
  2303. rrdththBin.append(None)
  2304. else:
  2305. if minorBin[-1] == 0: # Should NEVER happen
  2306. aspectRatioBin.append(1e6)
  2307. else:
  2308. aspectRatioBin.append(majorBin[-1] / minorBin[-1])
  2309. angleBin.append(self.angle[frame][bins[b][i]])
  2310. ax_ththBin.append(None)
  2311. ax_rrBin.append(None)
  2312. ax_ththBin[-1], ax_rrBin[
  2313. -1
  2314. ] = EllipseFitter.GetRotatedMajorMinor(
  2315. majorBin[-1], minorBin[-1], angleBin[-1], wa * 180 / np.pi
  2316. )
  2317. if ax_ththBin[-1] == 0: # Should NEVER happen
  2318. rrdththBin.append(1e6)
  2319. else:
  2320. rrdththBin.append(ax_rrBin[-1] / ax_ththBin[-1])
  2321. # print('pa_angle and woundAngle:',angleBin[-1],wa*180/np.pi)
  2322. # print('principle axes:',majorBin[-1],minorBin[-1])
  2323. # print('"radial" axes',ax_rrBin[-1],ax_ththBin[-1])
  2324. # print('square comparison (for good measure...)', \
  2325. # majorBin[-1]**2+minorBin[-1]**2,ax_rrBin[-1]**2+ax_ththBin[-1]**2)
  2326. majorMeans[-1].append(meanN(majorBin))
  2327. minorMeans[-1].append(meanN(minorBin))
  2328. aspectRatioMeans[-1].append(meanN(aspectRatioBin))
  2329. angleMeans[-1].append(meanN(angleBin))
  2330. ax_ththMeans[-1].append(meanN(ax_ththBin))
  2331. ax_rrMeans[-1].append(meanN(ax_rrBin))
  2332. rrdththMeans[-1].append(meanN(rrdththBin))
  2333. excelOut = [
  2334. majorMeans,
  2335. minorMeans,
  2336. aspectRatioMeans,
  2337. angleMeans,
  2338. ax_ththMeans,
  2339. ax_rrMeans,
  2340. rrdththMeans,
  2341. ]
  2342. sheetNames = [
  2343. "Major",
  2344. "Minor",
  2345. "Aspect Ratio",
  2346. "Angle",
  2347. "Irr",
  2348. "Iqq",
  2349. "Irr D Iqq",
  2350. ]
  2351. ExcelHelper.excelWrite(
  2352. excelOut, sheetNames, os.path.join(d, "PrincipalAxes.xls"), flip=False
  2353. )
  2354. class WatershedData(WatershedDataCoreWithStats):
  2355. """Enhances WatershedDataCoreWithStats to include plotting"""
  2356. def __init__(
  2357. self, arrayIn, previousSeeds=None, fig1=None, ax1=None, fig2=None, ax2=None
  2358. ):
  2359. WatershedDataCoreWithStats.__init__(self, arrayIn, previousSeeds=previousSeeds)
  2360. self.fig1 = fig1 if fig1 else plt.figure(1)
  2361. self.fig2 = fig2 if fig2 else plt.figure(2)
  2362. self.ax1 = ax1 if ax1 else self.fig1.add_subplot(111)
  2363. self.ax2 = ax2 if ax2 else self.fig2.add_subplot(111)
  2364. # self.fig3 = plt.figure(3) # make this as-needed instead...
  2365. # self.ax3 = self.fig3.add_subplot(111) # make this as-needed instead...
  2366. def Save(self, d, saveOutlines=True, saveMapImages=True): # Ignore Undo
  2367. WatershedDataCoreWithStats.Save(self, d, saveOutlines=saveOutlines)
  2368. mapBase = os.path.join(d, "Maps", "Map")
  2369. # Save Map Images
  2370. if saveMapImages:
  2371. oldIndex = self.index
  2372. plt.ioff()
  2373. for i in range(self.length):
  2374. self.index = i
  2375. if self.framesVisited[i]:
  2376. self.MapPlot(saveFile=mapBase + str(i) + ".png")
  2377. self.index = oldIndex
  2378. def Open(self, d):
  2379. if WatershedDataCoreWithStats.Open(self, d):
  2380. self.ColorPlot()
  2381. self.MapPlot()
  2382. def _plotUpdate(self):
  2383. self.framesVisited[self.index] = True
  2384. self.MapPlot()
  2385. self.ColorPlot()
  2386. def NextFrame(self, doPlots=True):
  2387. WatershedDataCoreWithStats.NextFrame(self)
  2388. if self.index + 1 < self.length:
  2389. if doPlots: # If we skip over it, no need to save...
  2390. self._plotUpdate()
  2391. def PreviousFrame(self, doPlots=True):
  2392. WatershedDataCoreWithStats.PreviousFrame(self)
  2393. if doPlots: # If we skip over it, no need to save...
  2394. self._plotUpdate()
  2395. def MoveToFrame(self, newIndex):
  2396. WatershedDataCoreWithStats.MoveToFrame(self, newIndex)
  2397. self._plotUpdate()
  2398. def GoToLastVisitedFrame(self):
  2399. self.MoveToFrame(self.lastFrameVisited)
  2400. def Invert(self):
  2401. """This only changes how the image is displayed (normal or inverted) - real data is still "white-side-up"
  2402. This behavior was changed in version 0.5.5.0; """
  2403. self.showInverted = not self.showInverted
  2404. self.ColorPlot()
  2405. # Old behavior:
  2406. # self.filterData[self.index] = self.filterData[self.index].max()-self.filterData[self.index]
  2407. # self.RemoveUndo()
  2408. # def DrawOrigImage(self): # deprecated
  2409. def MapPlot(self, saveFile=None, useText=False):
  2410. # plt.figure(2)#;cla()
  2411. wi = np.array(self.watershed[self.index], dtype=np.int)
  2412. if (
  2413. saveFile is not None and useText
  2414. ): # Normally skip all the text labels... they are painfully slow!
  2415. for i in self.mapCentroids:
  2416. if i is not None:
  2417. i.set_visible(False)
  2418. for i in self.GetActiveSeedValues():
  2419. if i > 0:
  2420. cm = center_of_mass((wi == i).astype(np.float))
  2421. if math.isnan(cm[0]) or math.isnan(cm[1]):
  2422. print("CM is NAN")
  2423. print("val", i)
  2424. print(cm[0])
  2425. print(cm[1])
  2426. else:
  2427. if i >= len(self.mapCentroids):
  2428. self.mapCentroids += [None] * (
  2429. 1 + i - len(self.mapCentroids)
  2430. )
  2431. x, y = int(round(cm[0])), int(round(cm[1]))
  2432. if self.mapCentroids[i] is not None:
  2433. self.mapCentroids[i].set_position([y - 5, x + 5])
  2434. self.mapCentroids[i].set_visible(True)
  2435. else:
  2436. self.mapCentroids[i] = self.ax2.text(
  2437. y - 5, x + 5, str(i), fontsize=10
  2438. )
  2439. # self.mapCentroids[i].set_visible(True)
  2440. # Never actually remove the objects, just make them invisible...
  2441. # Sneaky...
  2442. self.fig2.canvas.draw()
  2443. self.fig2.savefig(saveFile)
  2444. self.ax2.cla()
  2445. elif saveFile is not None: # I may have broken this... sorry...
  2446. f1 = np.vectorize(lambda x: self.mapPlotRandomArray[0][x])
  2447. f2 = np.vectorize(lambda x: self.mapPlotRandomArray[1][x])
  2448. f3 = np.vectorize(lambda x: self.mapPlotRandomArray[2][x])
  2449. if self.rgbM is None:
  2450. self.rgbM = np.ascontiguousarray(
  2451. np.array([f1(wi), f2(wi), f3(wi)], dtype=np.uint8).transpose(
  2452. 1, 2, 0
  2453. )
  2454. )
  2455. else:
  2456. # No more dependence on cython function; save will be a little slower, but whatever...
  2457. # convToRandColors(self.mapPlotRandomArray,self.rgbM,
  2458. # wi,wi.shape[0],wi.shape[1])
  2459. # This step is still really slow...
  2460. self.rgbM[:, :, 0] = f1(self.watershed[self.index])
  2461. self.rgbM[:, :, 1] = f2(self.watershed[self.index])
  2462. self.rgbM[:, :, 2] = f3(self.watershed[self.index])
  2463. im = Image.fromarray(self.rgbM)
  2464. im.save(saveFile)
  2465. # imsave(saveFile, map)
  2466. else:
  2467. if self.mapPlot is not None:
  2468. ###self.mapPlot.set_data(self.rgbM)
  2469. self.mapPlot.set_data(self.watershed[self.index])
  2470. else:
  2471. ###self.mapPlot = self.ax2.imshow(self.rgbM,interpolation='nearest',animated=True)
  2472. self.mapPlot = self.ax2.imshow(
  2473. self.watershed[self.index],
  2474. animated=True,
  2475. interpolation="nearest",
  2476. cmap=self.mapPlotCmap,
  2477. norm=matplotlib.colors.NoNorm(),
  2478. )
  2479. self.DrawBWDot()
  2480. # self.fig2.canvas.draw() # Now DrawBWDot calls this instead...
  2481. def MapPlotWTracks(self):
  2482. # Disable the old mapPlot and redraw every time (will be slow...)
  2483. # Later, could possibly store the tracks part for animations, too...
  2484. self.mapPlot = None
  2485. self.MapPlot()
  2486. if self.centroidX and self.centroidY:
  2487. x = np.array(self.centroidX)
  2488. y = np.array(self.centroidY)
  2489. else:
  2490. self.UpdateValuesList(index)
  2491. x = []
  2492. y = []
  2493. for index in range(self.length):
  2494. if self.sparseList[index] is None:
  2495. break
  2496. elif self.sparseList[index].nnz == 0:
  2497. break
  2498. centroid = self.GetCentroids(index, doUpdate=False)
  2499. x.append([i[0] for i in centroid])
  2500. y.append([i[1] for i in centroid])
  2501. x = np.array(x)
  2502. y = n.array(y)
  2503. self.ax2.plot(y[0], x[0], "kx")
  2504. self.ax2.plot(y, x, "k")
  2505. # initiate the mouse-over printing...
  2506. self.ax2.format_coord = GetReportPixel(self)
  2507. def ToggleOverlaysVisible(self):
  2508. self.overlayVisible = not self.overlayVisible
  2509. self.ColorPlot()
  2510. def ColorPlot(self):
  2511. # plt.figure(1)
  2512. im = self.filterData[self.index] # NOT 8- bit!!!
  2513. if self.showInverted:
  2514. im = im.max() - im
  2515. seed = (self.seedArray != 0).astype(np.uint8)
  2516. outl = self.woutline.astype(np.uint8)
  2517. seedORoutl = np.array(np.array(seed + outl) > 0, np.uint8)
  2518. if self.rgba is None:
  2519. self.rgba = np.array(
  2520. [
  2521. 0 * seed,
  2522. 255 * seed,
  2523. 255 * outl,
  2524. self.overlayVisible * 255 * seedORoutl,
  2525. ]
  2526. ).transpose(1, 2, 0)
  2527. else:
  2528. self.rgba[:, :, 0] = 0 * seed
  2529. self.rgba[:, :, 1] = 255 * seed
  2530. self.rgba[:, :, 2] = 255 * outl
  2531. self.rgba[:, :, 3] = self.overlayVisible * 255 * seedORoutl
  2532. # 0.18s
  2533. if self.colorplot is not None and self.bgPlot is not None:
  2534. self.bgPlot.set_data(im)
  2535. self.colorplot.set_data(self.rgba)
  2536. else:
  2537. self.bgPlot = self.ax1.imshow(
  2538. im, cmap=plt.cm.gray, interpolation="nearest", animated=True
  2539. )
  2540. self.colorplot = self.ax1.imshow(
  2541. self.rgba, interpolation="nearest", animated=True
  2542. )
  2543. self.ax1.set_xlim(0, self.watershed[0].shape[1])
  2544. self.ax1.set_ylim(self.watershed[0].shape[0], 0)
  2545. dprint(["wc", self.woundCenters[self.index], self.showWoundCenters])
  2546. # Need to force this to plot so that axes will flip, just set to invisible...
  2547. if self.redDot is None:
  2548. self.redDot = self.ax1.plot([0], [0], "ro")
  2549. self.ax1.set_xlim(0, self.watershed[0].shape[1])
  2550. self.ax1.set_ylim(self.watershed[0].shape[0], 0)
  2551. if self.showWoundCenters and self.woundCenters[self.index] is not None:
  2552. [x, y] = self.woundCenters[self.index]
  2553. self.redDot[0].set_data([x], [y])
  2554. self.redDot[0].set_visible(True) # Turn On Visible
  2555. else:
  2556. self.redDot[0].set_visible(False) # Turn Off Visible
  2557. # self.ax1.set_xlim(0,self.watershed[0].shape[1])
  2558. # self.ax1.set_ylim(self.watershed[0].shape[0],0)
  2559. # AAARRRGGG!!!
  2560. # NEED TO FORCE COORDINATES TO STAY IMAGE-STYLE!
  2561. # This is by FAR the slowest step, about 0.25s
  2562. self.HighlightRegion()
  2563. # self.fig1.canvas.draw() # Highlight Region also calls draw, so this doesn't need to!
  2564. def DrawBWDot(self):
  2565. # plt.figure(2)
  2566. if self.point_mode == False:
  2567. cm = np.zeros([len(self.selectionVals), 2], dtype=np.float)
  2568. for i, v in enumerate(self.selectionVals):
  2569. if v > 1:
  2570. # centroid of selected region v
  2571. cm[i] = center_of_mass(
  2572. (self.watershed[self.index] == v).astype(np.float)
  2573. )
  2574. if self.bwDot is None and self.point_mode == False:
  2575. self.bwDot = self.ax2.plot(cm[:, 1], cm[:, 0], "ko")
  2576. self.bwDot[0].set_markeredgecolor("w")
  2577. self.bwDot[0].set_markeredgewidth(1.2)
  2578. else:
  2579. self.bwDot[0].set_data(cm[:, 1], cm[:, 0])
  2580. self.fig2.canvas.draw()
  2581. def HighlightRegion(self):
  2582. a = np.zeros(self.shape[1:], dtype=np.uint8)
  2583. if self.point_mode:
  2584. a[:] = self.seedSelections[self.index].toarray().astype(np.uint8) * 255
  2585. if self.rgbaH is None:
  2586. self.rgbaH = np.array([a, a * 0, a, a]).transpose(1, 2, 0)
  2587. else:
  2588. self.rgbaH[:, :, 0] = a
  2589. self.rgbaH[:, :, 1] = a * 0
  2590. self.rgbaH[:, :, 2] = a
  2591. self.rgbaH[:, :, 3] = a
  2592. else:
  2593. for i in self.selectionVals:
  2594. if i > 0:
  2595. a = a + 255 * (
  2596. self.watershed[self.index] == i
  2597. ) # Changed from += due to a regression in numpy
  2598. if self.rgbaH is None:
  2599. self.rgbaH = np.array([a, a, a * 0, a // 2]).transpose(1, 2, 0)
  2600. else:
  2601. self.rgbaH[:, :, 0] = (
  2602. a // 2 - 1
  2603. ) # These 2 lines used to be just "=a", but then the behavior of
  2604. self.rgbaH[:, :, 1] = (
  2605. a // 2 - 1
  2606. ) # matplotlib changed and the yellow highlight turned gray
  2607. self.rgbaH[:, :, 2] = a * 0
  2608. self.rgbaH[:, :, 3] = a // 2
  2609. self.DrawBWDot()
  2610. if self.hiplot is not None:
  2611. self.hiplot.set_data(self.rgbaH)
  2612. else:
  2613. self.hiplot = self.ax1.imshow(
  2614. self.rgbaH, interpolation="nearest", animated=True
  2615. )
  2616. self.fig1.canvas.draw()
  2617. def LassoCallback(self, verts):
  2618. verts = [v[::-1] for v in verts]
  2619. self.SetUndoPoint()
  2620. # Clear selections...
  2621. self.seedSelections[self.index] = scipy.sparse.lil_matrix(
  2622. self.shape[1:], dtype=np.bool
  2623. )
  2624. cooMat = self.sparseList[self.index].tocoo()
  2625. seedList = np.array([cooMat.row, cooMat.col]).T
  2626. wh = np.where(points_inside_poly(seedList, verts))
  2627. row = np.array([cooMat.row[i] for i in wh])
  2628. col = np.array([cooMat.col[i] for i in wh])
  2629. # This should be faster for big selections
  2630. sel = self.seedSelections[self.index].toarray()
  2631. sel[(row, col)] = 1
  2632. self.seedSelections[self.index] = scipy.sparse.lil_matrix(sel)
  2633. self.point_mode = True
  2634. self.ColorPlot()
  2635. self.fig1.canvas.draw_idle()
  2636. self.fig1.canvas.widgetlock.release(self.lasso)
  2637. del self.lasso
  2638. def PlotAreasAndPerimeters(self): # Must run CollectAllStats first!
  2639. area = np.array(self.area)
  2640. per = np.array(self.perimeter)
  2641. for v in range(area.shape[1]):
  2642. self.fig4 = plt.figure(4)
  2643. self.ax4 = self.fig4.add_subplot(111)
  2644. self.ax4.plot(area[:, v], label=str(v))
  2645. self.fig5 = plt.figure(5)
  2646. self.ax5 = self.fig5.add_subplot(111)
  2647. self.ax5.plot(per[:, v], label=str(v))
  2648. self.ax4.legend()
  2649. self.ax5.legend()
  2650. def TestCalculations(self):
  2651. self.UpdateValuesList()
  2652. print(self.valList)
  2653. print(self.GetBoundingRectangles())
  2654. print(self.CalculateCentroids())
  2655. for [[xm, xM], [ym, yM]] in self.GetBoundingRectangles():
  2656. self.ax2.plot([ym, ym, yM, yM, ym], [xm, xM, xM, xm, xm], "k-")
  2657. print(self.CalculateArea())
  2658. print(self.CalculatePerimeter())
  2659. print(self.CalculateBestFitEllipse())
  2660. # print(self.CalculateWoundDistance())
  2661. print(self.CalculateNeighbors())
  2662. def RunCalculations2(self, d):
  2663. WatershedDataCoreWithStats.RunCalculations2(self, d)
  2664. oldIndex = self.index
  2665. self.index = 0
  2666. self.MapPlot(saveFile=os.path.join(d, "MapWithCellIDs.png"), useText=True)
  2667. self.mapCentroids = []
  2668. self.mapPlot = None
  2669. self.index = oldIndex
  2670. self.MapPlot()
  2671. print("Finished")
  2672. def WCPlot(self, ind, woundVals, WNv, NNv):
  2673. wi = np.array(self.watershed[ind], dtype=np.int)
  2674. for v in woundVals:
  2675. wi[np.where(wi == v)] = 1e6
  2676. self.fig10 = plt.figure(10)
  2677. self.fig10.clf()
  2678. self.ax10 = self.fig10.add_subplot(111)
  2679. self.ax10.set_ylim(self.ax10.get_ylim()[::-1])
  2680. for v in WNv[ind]:
  2681. for c in ImageContour.GetBoundaryLine(wi, 1e6, v):
  2682. self.ax10.plot(*(np.array(c)[:, ::-1].T), marker=".")
  2683. sleep(0.4)
  2684. self.fig10.canvas.draw()
  2685. for v in NNv[ind]:
  2686. for c in ImageContour.GetBoundaryLine(wi, v[0], v[1]):
  2687. self.ax10.plot(*(np.array(c)[:, ::-1].T))
  2688. sleep(0.4)
  2689. self.fig10.canvas.draw()
  2690. def RingsPlot(
  2691. self, bins, frame, woundVals, useNei=False, binSize=None, initR=0
  2692. ): # still using pyplot directly...
  2693. # b,g,r,c,m,y,k
  2694. colors = [
  2695. [0, 0, 255],
  2696. [0, 255, 0],
  2697. [255, 0, 0],
  2698. [0, 255, 255],
  2699. [255, 0, 255],
  2700. [255, 255, 0],
  2701. ] * 10 # that ought to do it ;-P
  2702. rgbMap4Rings = 255 * np.ones(
  2703. list(self.watershed[0].shape) + [3], dtype=np.uint8
  2704. )
  2705. for i, w in enumerate(woundVals):
  2706. rgbMap4Rings[np.where(self.watershed[frame] == w)] = colors[i]
  2707. for i in range(len(bins)):
  2708. for j in range(len(bins[i])):
  2709. ind = i + 2
  2710. val = bins[i][j] + 2
  2711. rgbMap4Rings[np.where(self.watershed[frame] == val)] = colors[
  2712. ind - 2 + len(woundVals)
  2713. ]
  2714. plt.imshow(rgbMap4Rings)
  2715. a = plt.gca()
  2716. if not useNei:
  2717. cir = plt.Circle(
  2718. (self.woundCenters[0][0], self.woundCenters[0][1]),
  2719. radius=0.1,
  2720. Fill=False,
  2721. )
  2722. a.add_patch(cir)
  2723. for i in range(len(bins)):
  2724. cir = plt.Circle(
  2725. (self.woundCenters[0][0], self.woundCenters[0][1]),
  2726. radius=initR + i * binSize,
  2727. Fill=False,
  2728. )
  2729. a.add_patch(cir)
  2730. plt.draw()
  2731. return rgbMap4Rings
  2732. mouseModeHelpTxt = [
  2733. "Move Mode Help:\n"
  2734. " Left and Right Click defer to toolbar\n"
  2735. " Try out the pan and zoom in the toolbar",
  2736. "Add/Delete Mode Help:\n" " Left Click = Add\n" " Right Click = Delete",
  2737. "Lasso Mode Help:\n"
  2738. " Left Click = Draw (can also drag)\n"
  2739. " Right Click = Close loop, select points",
  2740. "Extra Mode Help:\n"
  2741. " Left Click = Select Region\n"
  2742. " Right Click = Create Extra Seed For Selected Region ",
  2743. "Switch Mode Help:\n"
  2744. " Left Click = Select Region\n"
  2745. " Right Click = Swap With Another Region",
  2746. "Draw Mode Help:\n" " Left Click = Select Region\n" " Right Click = Draw!",
  2747. "Center Mode Help:\n"
  2748. " Left Click = Select Wound Center\n"
  2749. " Right Click = Select Wound Center for ALL Frames After",
  2750. ]
  2751. class SegmenterFrame(wx.Frame):
  2752. def __init__(self, *args, **kwds):
  2753. kwds["style"] = wx.DEFAULT_FRAME_STYLE
  2754. wx.Frame.__init__(self, *args, **kwds)
  2755. self.MainPanel = wx.Panel(self, -1)
  2756. self.FilenameText = wx.TextCtrl(self.MainPanel, -1, "")
  2757. # Needs Tooltips instead!!
  2758. self.MouseModeRadioBox = wx.RadioBox(
  2759. self.MainPanel,
  2760. -1,
  2761. "Mouse Mode",
  2762. choices=[
  2763. "Move Mode (Default)",
  2764. "Add/Delete Mode",
  2765. "Lasso Mode",
  2766. "Extra Seed (Highlight) Mode",
  2767. "Switch Regions Mode",
  2768. "Draw Mode",
  2769. "Center Mode",
  2770. ],
  2771. majorDimension=0,
  2772. style=wx.RA_SPECIFY_ROWS,
  2773. )
  2774. self.MouseModeHelpText = wx.StaticText(self.MainPanel, -1, mouseModeHelpTxt[3])
  2775. self.FrameNumber = wx.SpinCtrl(self.MainPanel, -1, "", min=0, max=1)
  2776. self.FrameNumberText = wx.StaticText(self.MainPanel, -1, "Frame Number")
  2777. self.ToggleViewCheckbox = wx.CheckBox(
  2778. self.MainPanel, -1, "Show Segmentation Overlays?"
  2779. )
  2780. self.InvertViewCheckbox = wx.CheckBox(
  2781. self.MainPanel, -1, "Show Image Inverted?"
  2782. )
  2783. self.CellNumber = wx.SpinCtrl(self.MainPanel, -1, "", min=1, max=(2 ** 16 - 1))
  2784. self.CellNumberText = wx.StaticText(self.MainPanel, -1, "Selected Cell ID")
  2785. self.HighlightListOfCellIDsButton = wx.Button(
  2786. self.MainPanel, -1, "Highlight a List of Cell IDs"
  2787. )
  2788. self.NotesTextBox = wx.TextCtrl(
  2789. self.MainPanel,
  2790. -1,
  2791. "Type Notes Here (per frame)",
  2792. style=wx.TE_PROCESS_ENTER | wx.TE_MULTILINE,
  2793. )
  2794. self.ReSaveAllFramesButton = wx.Button(self.MainPanel, -1, "Re-Save All Frames")
  2795. self.ReRunAllWatershedsButton = wx.Button(
  2796. self.MainPanel, -1, "Re-Run All Watersheds"
  2797. )
  2798. self.CompressSeedValuesButton = wx.Button(
  2799. self.MainPanel, -1, "Compress Seed Values"
  2800. )
  2801. self.AutoCenterWoundButton = wx.Button(self.MainPanel, -1, "Auto Center Wound")
  2802. self.RunCalculationsButton = wx.Button(
  2803. self.MainPanel, -1, "Run Calculations for All Frames"
  2804. )
  2805. self.RunCalculations2Button = wx.Button(
  2806. self.MainPanel, -1, "Run Calculations (2) for All Frames"
  2807. )
  2808. self.CheckForMalformedRegionsButton = wx.Button(
  2809. self.MainPanel, -1, "Check for malformed regions"
  2810. )
  2811. self.StatusText = wx.StaticText(
  2812. self.MainPanel, -1, "Status:\n Initializing..."
  2813. )
  2814. self.SummaryText = wx.StaticText(
  2815. self.MainPanel,
  2816. -1,
  2817. """
  2818. Summary of Key Commands:
  2819. """
  2820. + letterKeys
  2821. + """
  2822. 1-9: Change size of drawing circle in Extra/Draw Mode
  2823. Delete/Backspace: Delete selected points after lasso
  2824. Shift + Delete/Backspace: Delete unselected points after lasso
  2825. Arrow Keys: Move selected seeds (after lasso)
  2826. + (up) These keys are an alternative
  2827. [ ] (left/right) since arrow keys are having
  2828. " (down) problems on Windows.
  2829. (BTW, you have to use Ctrl in this window, but not in others)
  2830. """,
  2831. )
  2832. self.SetTitle("SeedWater Segmenter")
  2833. self.MouseModeRadioBox.SetSelection(0)
  2834. self.NotesTextBox.SetInitialSize(
  2835. (-1, 100)
  2836. ) # Set the initial height to be 100px
  2837. self.menuBar = wx.MenuBar()
  2838. self.FileMenu = wx.Menu()
  2839. item = self.FileMenu.Append(
  2840. -1, "O&pen Stack or Sequence (Gif/Tiff)\tCtrl-O", "Open Gif/Tiff"
  2841. )
  2842. self.Bind(wx.EVT_MENU, (lambda event: self.HandleMPLandWxKeyEvents("o")), item)
  2843. item = self.FileMenu.Append(-1, "E&xit\tCtrl-Q", "Exit demo")
  2844. self.Bind(wx.EVT_MENU, (lambda event: self.HandleMPLandWxKeyEvents("q")), item)
  2845. self.menuBar.Append(self.FileMenu, "&File")
  2846. self.CommandMenu = wx.Menu()
  2847. for keyCommand in letterKeys.split("\n"):
  2848. key = keyCommand[0].upper()
  2849. descr = keyCommand[3:]
  2850. if key in "QO":
  2851. continue
  2852. item = self.CommandMenu.Append(
  2853. -1, "&" + key + " - " + descr + "\tCtrl-" + key, ""
  2854. )
  2855. def Temp(event):
  2856. ekey = self.CommandMenu.FindItemById(event.Id).Label[0].lower()
  2857. self.HandleMPLandWxKeyEvents(ekey)
  2858. print(ekey)
  2859. self.Bind(wx.EVT_MENU, Temp, item)
  2860. self.menuBar.Append(self.CommandMenu, "&Command")
  2861. self.SetMenuBar(self.menuBar)
  2862. mainSizer = wx.BoxSizer(wx.HORIZONTAL)
  2863. panelVSizer = wx.BoxSizer(wx.VERTICAL)
  2864. splitHSizer = wx.BoxSizer(wx.HORIZONTAL)
  2865. controlsVSizer = wx.BoxSizer(wx.VERTICAL)
  2866. frameNumberHSizer = wx.BoxSizer(wx.HORIZONTAL)
  2867. checkboxHSizer = wx.BoxSizer(wx.HORIZONTAL)
  2868. cellNumberHSizer = wx.BoxSizer(wx.HORIZONTAL)
  2869. reHSizer = wx.BoxSizer(wx.HORIZONTAL)
  2870. compressCenterCheckHSizer = wx.BoxSizer(wx.HORIZONTAL)
  2871. calculationsHSizer = wx.BoxSizer(wx.HORIZONTAL)
  2872. panelVSizer.Add(self.FilenameText, 0, wx.EXPAND, 0)
  2873. panelVSizer.Add((10, 10), 0, 0, 0)
  2874. frameNumberHSizer.Add(self.FrameNumber, 0, 0, 0)
  2875. frameNumberHSizer.Add(self.FrameNumberText, 0, 0, 0)
  2876. checkboxHSizer.Add(self.ToggleViewCheckbox, 0, 0, 0)
  2877. checkboxHSizer.Add((10, 10), 0, 0, 0)
  2878. checkboxHSizer.Add(self.InvertViewCheckbox, 0, 0, 0)
  2879. cellNumberHSizer.Add(self.CellNumber, 0, 0, 0)
  2880. cellNumberHSizer.Add(self.CellNumberText, 0, 0, 0)
  2881. cellNumberHSizer.Add((10, 10), 0, 0, 0)
  2882. cellNumberHSizer.Add(self.HighlightListOfCellIDsButton, 0, 0, 0)
  2883. reHSizer.Add(self.ReSaveAllFramesButton, 0, 0, 0)
  2884. reHSizer.Add((10, 10), 0, 0, 0)
  2885. reHSizer.Add(self.ReRunAllWatershedsButton, 0, 0, 0)
  2886. compressCenterCheckHSizer.Add(self.CompressSeedValuesButton, 0, 0, 0)
  2887. compressCenterCheckHSizer.Add((10, 10), 0, 0, 0)
  2888. compressCenterCheckHSizer.Add(self.AutoCenterWoundButton, 0, 0, 0)
  2889. compressCenterCheckHSizer.Add((10, 10), 0, 0, 0)
  2890. compressCenterCheckHSizer.Add(self.CheckForMalformedRegionsButton, 0, 0, 0)
  2891. calculationsHSizer.Add(self.RunCalculationsButton, 0, 0, 0)
  2892. calculationsHSizer.Add((10, 10), 0, 0, 0)
  2893. calculationsHSizer.Add(self.RunCalculations2Button, 0, 0, 0)
  2894. controlsVSizer.Add(self.MouseModeRadioBox, 0, 0, 0)
  2895. controlsVSizer.Add((10, 10), 0, 0, 0)
  2896. controlsVSizer.Add(self.MouseModeHelpText, 0, 0, 0)
  2897. controlsVSizer.Add((10, 10), 0, 0, 0)
  2898. controlsVSizer.Add(frameNumberHSizer, 0, 0, 0)
  2899. controlsVSizer.Add((10, 10), 0, 0, 0)
  2900. controlsVSizer.Add(checkboxHSizer, 0, 0, 0)
  2901. controlsVSizer.Add((10, 10), 0, 0, 0)
  2902. controlsVSizer.Add(cellNumberHSizer, 0, 0, 0)
  2903. controlsVSizer.Add((10, 10), 0, 0, 0)
  2904. controlsVSizer.Add(self.NotesTextBox, 0, wx.EXPAND, 0)
  2905. controlsVSizer.Add((10, 10), 0, 0, 0)
  2906. controlsVSizer.Add(reHSizer, 0, 0, 0)
  2907. controlsVSizer.Add((10, 10), 0, 0, 0)
  2908. controlsVSizer.Add(compressCenterCheckHSizer, 0, 0, 0)
  2909. controlsVSizer.Add((10, 10), 0, 0, 0)
  2910. controlsVSizer.Add(calculationsHSizer, 0, 0, 0)
  2911. controlsVSizer.Add((10, 10), 0, 0, 0)
  2912. controlsVSizer.Add(self.StatusText, 0, wx.EXPAND, 0)
  2913. splitHSizer.Add(controlsVSizer, 1, wx.EXPAND, 0)
  2914. splitHSizer.Add((20, 20), 0, 0, 0)
  2915. splitHSizer.Add(self.SummaryText, 0, 0, 0)
  2916. panelVSizer.Add(splitHSizer, 1, wx.EXPAND, 0)
  2917. self.MainPanel.SetSizer(panelVSizer)
  2918. mainSizer.Add(self.MainPanel, 1, wx.EXPAND, 0)
  2919. self.SetSizer(mainSizer)
  2920. mainSizer.Fit(self)
  2921. self.Layout()
  2922. self.shiftDown = False
  2923. for i in [
  2924. self,
  2925. self.MainPanel,
  2926. self.FilenameText,
  2927. self.MouseModeRadioBox,
  2928. self.MouseModeHelpText,
  2929. self.FrameNumber,
  2930. self.FrameNumberText,
  2931. self.ToggleViewCheckbox,
  2932. self.InvertViewCheckbox,
  2933. self.CellNumber,
  2934. self.CellNumberText,
  2935. self.HighlightListOfCellIDsButton,
  2936. self.NotesTextBox,
  2937. self.ReSaveAllFramesButton,
  2938. self.ReRunAllWatershedsButton,
  2939. self.CompressSeedValuesButton,
  2940. self.AutoCenterWoundButton,
  2941. self.RunCalculationsButton,
  2942. self.RunCalculations2Button,
  2943. self.CheckForMalformedRegionsButton,
  2944. self.StatusText,
  2945. self.SummaryText,
  2946. ]:
  2947. i.Bind(wx.EVT_KEY_DOWN, self.OnKeyDownWx)
  2948. self.Bind(wx.EVT_TEXT_ENTER, self.FileNameTextCallback, self.FilenameText)
  2949. self.Bind(
  2950. wx.EVT_RADIOBOX, self.MouseModeRadioBoxCallback, self.MouseModeRadioBox
  2951. )
  2952. self.Bind(wx.EVT_SPINCTRL, self.FrameNumberCallback, self.FrameNumber)
  2953. self.Bind(
  2954. wx.EVT_CHECKBOX, self.ToggleViewCheckBoxCallback, self.ToggleViewCheckbox
  2955. )
  2956. self.Bind(
  2957. wx.EVT_CHECKBOX, self.InvertViewCheckBoxCallback, self.InvertViewCheckbox
  2958. )
  2959. self.Bind(wx.EVT_SPINCTRL, self.CellNumberCallback, self.CellNumber)
  2960. self.Bind(
  2961. wx.EVT_BUTTON,
  2962. self.HighlightListOfCellIDsCallback,
  2963. self.HighlightListOfCellIDsButton,
  2964. )
  2965. # self.Bind(wx.EVT_TEXT, self.NotesTextBoxCallback, self.NotesTextBox)
  2966. self.Bind(
  2967. wx.EVT_BUTTON, self.ReSaveAllFramesCallback, self.ReSaveAllFramesButton
  2968. )
  2969. self.Bind(
  2970. wx.EVT_BUTTON,
  2971. self.ReRunAllWatershedsCallback,
  2972. self.ReRunAllWatershedsButton,
  2973. )
  2974. self.Bind(
  2975. wx.EVT_BUTTON,
  2976. self.CompressSeedValuesCallback,
  2977. self.CompressSeedValuesButton,
  2978. )
  2979. self.Bind(
  2980. wx.EVT_BUTTON, self.AutoCenterWoundCallback, self.AutoCenterWoundButton
  2981. )
  2982. self.Bind(
  2983. wx.EVT_BUTTON, self.RunCalculationsCallback, self.RunCalculationsButton
  2984. )
  2985. self.Bind(
  2986. wx.EVT_BUTTON, self.RunCalculations2Callback, self.RunCalculations2Button
  2987. )
  2988. self.Bind(
  2989. wx.EVT_BUTTON,
  2990. self.CheckForMalformedRegionsCallback,
  2991. self.CheckForMalformedRegionsButton,
  2992. )
  2993. def OnInit(
  2994. self,
  2995. filename=None,
  2996. saveDir="",
  2997. setConnections=True,
  2998. fig1=None,
  2999. ax1=None,
  3000. fig2=None,
  3001. ax2=None,
  3002. ):
  3003. self.SetStatus("Loading Image Data")
  3004. self.filename = filename
  3005. try:
  3006. self.saveDir
  3007. except:
  3008. self.saveDir = os.path.split(self.filename)[0]
  3009. self.FilenameText.SetValue(self.filename)
  3010. g = GTL.LoadMonolithicOrSequenceSpecial(self.filename)
  3011. # if GTL.GetShapeMonolithicOrSequence(self.filename)[3]: #isSequence
  3012. # g=GTL.LoadFileSequence(os.path.split(self.filename)[0])
  3013. # else:
  3014. # g=GTL.LoadMonolithic(self.filename)
  3015. self.wd = None # Release the old data before we load the new...
  3016. self.wd = WatershedData(g, fig1=fig1, ax1=ax1, fig2=fig2, ax2=ax2)
  3017. self.FrameNumber.SetValue(0)
  3018. self.FrameNumber.SetRange(0, self.wd.length)
  3019. self.FrameNumberText.SetLabel(
  3020. "Frame Number (last - " + str(self.wd.length - 1) + ")\n(last active - 0)"
  3021. )
  3022. self.ToggleViewCheckbox.SetValue(self.wd.overlayVisible)
  3023. self.InvertViewCheckbox.SetValue(self.wd.showInverted)
  3024. self.CellNumber.SetValue(2)
  3025. self.CellNumber.SetRange(1, 2 ** 16 - 1)
  3026. self.CellNumberText.SetLabel("Selected Cell ID")
  3027. if setConnections:
  3028. self.SetMPLKeyConnections()
  3029. self.mouseMode = "m"
  3030. self.MouseModeHelpText.SetLabel(mouseModeHelpTxt[0])
  3031. self.SetConnection(self.HandleMPLMouseEvents)
  3032. # self.wd.DrawOrigImage()
  3033. self.wd.ax1.imshow(self.wd.filterData[self.wd.index], cmap=plt.cm.gray)
  3034. # Automatically ask to open previous seeds (just hit cancel if none...)
  3035. self.SetStatus("Checking on Optional Watershed and Seed Data")
  3036. msg = "Optionally Pick Directory Where Seed Info is Stored (Cancel if 1st use of program)"
  3037. if not saveDir:
  3038. saveDir = wx.DirSelector(msg, self.saveDir)
  3039. if not os.path.exists(saveDir):
  3040. print("Save Directory does not exist!")
  3041. print("Loading without save data")
  3042. saveDir = ""
  3043. if saveDir:
  3044. self.SetStatus("Loading Watershed and Seed Data")
  3045. self.saveDir = saveDir
  3046. self.wd.Open(saveDir)
  3047. self.UpdateFrameLabelText()
  3048. if self.wd.notes[self.wd.index] is None:
  3049. self.wd.notes[self.wd.index] = ""
  3050. self.NotesTextBox.SetValue(self.wd.notes[self.wd.index])
  3051. else:
  3052. self.HandleMPLandWxKeyEvents("g")
  3053. self.SetStatus("Ready")
  3054. def SetMPLKeyConnections(self):
  3055. for fig, ax in [
  3056. (self.wd.fig1, self.wd.ax1),
  3057. (self.wd.fig2, self.wd.ax2),
  3058. ]: # range(1,3):
  3059. fig.canvas.mpl_connect("key_press_event", self.OnKeyDownMPL)
  3060. fig.canvas.mpl_connect("key_release_event", self.OnKeyReleaseMPL)
  3061. ax.format_coord = GetReportPixel(
  3062. self
  3063. ) # Made it so we can also just pass self here...
  3064. def SetConnection(self, f):
  3065. self.connection = plt.connect("button_press_event", f)
  3066. def FileNameTextCallback(self, event):
  3067. f = event.GetValue()
  3068. if os.path.exists(f):
  3069. self.OnInit(f, setConnections=False)
  3070. d = wx.DirSelector(
  3071. "Optionally Pick Directory Where Seed Info is Stored", self.saveDir
  3072. )
  3073. if len(d) > 0:
  3074. self.saveDir = d
  3075. self.wd.Open(d)
  3076. def MouseModeRadioBoxCallback(self, event):
  3077. if event.GetInt() == 0:
  3078. ckey = "m"
  3079. elif event.GetInt() == 1:
  3080. ckey = "a"
  3081. elif event.GetInt() == 2:
  3082. ckey = "l"
  3083. elif event.GetInt() == 3:
  3084. ckey = "e"
  3085. elif event.GetInt() == 4:
  3086. ckey = "x"
  3087. elif event.GetInt() == 5:
  3088. ckey = "d"
  3089. elif event.GetInt() == 6:
  3090. ckey = "c"
  3091. else:
  3092. print("Invalid Mouse Mode!!!")
  3093. self.HandleMPLandWxKeyEvents(ckey)
  3094. def UpdateFrameLabelText(self):
  3095. self.FrameNumberText.SetLabel(
  3096. "Frame Number (last - "
  3097. + str(self.wd.length - 1)
  3098. + ")\n(last active - "
  3099. + str(self.wd.GetLastActiveFrame())
  3100. + ")"
  3101. )
  3102. def FrameNumberCallback(self, event):
  3103. ind = event.GetInt()
  3104. print("FrameNumber Callback with value", ind)
  3105. self.wd.notes[self.wd.index] = self.NotesTextBox.GetValue()
  3106. print(self.NotesTextBox.GetValue())
  3107. i2 = max(0, ind - 1)
  3108. if (
  3109. self.wd.sparseList[i2] is not None and ind < self.wd.length
  3110. ): # If frame before the one to move to is defined
  3111. if self.wd.sparseList[ind] is None:
  3112. self.SetStatus("Moving to New Frame " + str(ind))
  3113. else:
  3114. self.SetStatus("Moving to Frame " + str(ind))
  3115. if ind != self.wd.index:
  3116. self.wd.MoveToFrame(ind)
  3117. print("I'm in here!!")
  3118. self.UpdateFrameLabelText()
  3119. # if ind < self.wd.index:
  3120. # for i in range(self.wd.index-ind):
  3121. # self.wd.PreviousFrame(doPlots=False)
  3122. # elif ind > self.wd.index:
  3123. # for i in range(ind-self.wd.index):
  3124. # self.wd.NextFrame(doPlots=False)
  3125. # self.wd.ColorPlot()
  3126. # self.wd.MapPlot()
  3127. # self.wd.framesVisited[self.wd.index]=True
  3128. else:
  3129. self.FrameNumber.SetValue(self.wd.index)
  3130. if self.wd.notes[self.wd.index] is None:
  3131. self.wd.notes[self.wd.index] = ""
  3132. self.NotesTextBox.SetValue(self.wd.notes[self.wd.index])
  3133. self.SetStatus("Ready")
  3134. def ToggleViewCheckBoxCallback(self, event):
  3135. self.wd.ToggleOverlaysVisible()
  3136. def InvertViewCheckBoxCallback(self, event):
  3137. self.wd.Invert()
  3138. def CellNumberCallback(self, event):
  3139. cellid = event.GetInt()
  3140. print("CellNumber Callback with value", cellid)
  3141. self.wd.selectionVals = [cellid]
  3142. self.wd.previousDrawPoint = None
  3143. self.wd.ColorPlot()
  3144. def PlotListOfCellIDs(self, selectedCellIDs):
  3145. """Plots a list of cellIDs to help in quickly identifying sets of cells
  3146. (usually that are having problems...)
  3147. Uses figure 3"""
  3148. self.wd.fig3 = plt.figure(3)
  3149. self.wd.fig3.clf()
  3150. self.wd.ax3 = self.wd.fig3.add_subplot(111)
  3151. self.wd.ax3.format_coord = GetReportPixel(
  3152. self
  3153. ) # Made it so we can also just pass self here...
  3154. self.wd.ax3.imshow(
  3155. sum([(self.wd.watershed[self.wd.index] == v) * v for v in selectedCellIDs]),
  3156. interpolation="nearest",
  3157. cmap=self.wd.mapPlotCmap,
  3158. norm=matplotlib.colors.NoNorm(),
  3159. )
  3160. def HighlightListOfCellIDsCallback(self, event):
  3161. self.SetStatus("Getting a list of Cell IDs to plot...")
  3162. print("Asking for a list of Cell IDs")
  3163. dlg = wx.TextEntryDialog(
  3164. None, "Which Cell IDs do you want to plot?", "Cell IDs", "#,#"
  3165. )
  3166. if dlg.ShowModal() == wx.ID_OK:
  3167. self.SetStatus("Plotting Cell IDs")
  3168. print("Plot Cell IDs")
  3169. val = dlg.GetValue()
  3170. try:
  3171. selectedCellIDs = list(map(int, val.split(",")))
  3172. except:
  3173. print("Invalid Entry!!")
  3174. self.SetStatus("Ready")
  3175. return
  3176. if None not in selectedCellIDs:
  3177. self.PlotListOfCellIDs(selectedCellIDs)
  3178. self.SetStatus("Ready")
  3179. # def NotesTextBoxCallback(self,event):
  3180. # self.wd.notes[self.wd.index]=self.NotesTextBox.GetValue()
  3181. def ReSaveAllFramesCallback(self, event):
  3182. for i in range(self.wd.length):
  3183. if self.wd.sparseList[i] is not None:
  3184. self.wd.framesVisited[i] = True
  3185. self.HandleMPLandWxKeyEvents("s")
  3186. def ReRunAllWatershedsCallback(self, event):
  3187. t = time()
  3188. self.SetStatus("Test run of watershed (PyMorph algorithm)")
  3189. oldAlgo = self.wd.walgorithm[
  3190. self.wd.index
  3191. ] # make sure to put things back like we found them...
  3192. self.wd.UpdateSeeds()
  3193. self.wd.Watershed("PyMorph") # Ignore undo...
  3194. t = time() - t # see how long that took...
  3195. self.wd.ColorPlot()
  3196. self.wd.MapPlot()
  3197. print("Watershed Done -- took", t, "seconds.")
  3198. # Get the number of frames...
  3199. numFrames = self.wd.length
  3200. for i in range(self.wd.length):
  3201. if self.wd.sparseList[i] is None:
  3202. numFrames = i
  3203. break
  3204. self.SetStatus("Checking whether to run all frames")
  3205. dlg = wx.MessageDialog(
  3206. self,
  3207. "It will take approximately "
  3208. + str(t * numFrames)
  3209. + " seconds to run all watersheds.\n"
  3210. "Are you sure you want to continue?\n",
  3211. "Run Watershed on All Frames?",
  3212. wx.OK | wx.ICON_INFORMATION | wx.CANCEL,
  3213. )
  3214. self.wd.walgorithm[
  3215. self.wd.index
  3216. ] = oldAlgo # set algorithm back (in case it was different from PyMorph...)
  3217. if dlg.ShowModal() == wx.ID_OK:
  3218. t = time()
  3219. oldIndex = self.wd.index
  3220. for i in range(numFrames):
  3221. self.SetStatus(
  3222. "Running Watershed on Frame " + str(i) + " out of " + str(numFrames)
  3223. )
  3224. self.wd.index = i
  3225. self.wd.UpdateSeeds()
  3226. self.wd.Watershed(algorithm=self.wd.walgorithm[self.wd.index])
  3227. self.wd.framesVisited[i] = True
  3228. self.wd.index = oldIndex
  3229. self.wd.UpdateSeeds()
  3230. self.wd.ColorPlot()
  3231. self.wd.MapPlot()
  3232. print("All Watersheds Done -- took", time() - t, "seconds.")
  3233. else:
  3234. if oldAlgo != "PyMorph":
  3235. self.SetStatus("Running Watershed on Current Frame")
  3236. self.wd.Watershed(algorithm=oldAlgo)
  3237. self.wd.ColorPlot()
  3238. self.wd.MapPlot()
  3239. self.wd.framesVisited[self.wd.index] = True
  3240. print("Watershed Done")
  3241. self.SetStatus("Ready")
  3242. def CompressSeedValuesCallback(self, event):
  3243. t = time()
  3244. # 1. Get all seed values as a single list
  3245. # 2. Check to see which seed values are actually used, build a list of the values that are
  3246. # 3. Loop through all variables that involve seedVals (watershed, )
  3247. # * and starting with 2, move all the old values down to their new value
  3248. # (this should eliminate the same-seed convergence problem)
  3249. # 4. Check that everything did right...
  3250. # (4b. Remove the old seedValues???)
  3251. # 5. Replot the current frame as the underlying data should have changed...
  3252. self.wd.ColorPlot()
  3253. self.wd.MapPlot()
  3254. self.SetStatus("Checking whether to compress Seed Values")
  3255. dlg = wx.MessageDialog(
  3256. self,
  3257. "Compressing Seed Values removes unused seed values,\n"
  3258. "but it will change the colors of some or all cells.\n"
  3259. "Are you sure you want to continue?",
  3260. "Compress (Remove Unused) Seed Values?",
  3261. wx.OK | wx.ICON_INFORMATION | wx.CANCEL,
  3262. )
  3263. if dlg.ShowModal() == wx.ID_OK:
  3264. print("Change all seeds to eliminate unused seed values")
  3265. self.SetStatus("Changing all seeds to eliminate unused seed values")
  3266. self.wd.CompressSeedValues()
  3267. self.wd.ColorPlot()
  3268. self.wd.MapPlot()
  3269. print("Finished Compressing the values...")
  3270. self.SetStatus("Ready")
  3271. def AutoCenterWoundCallback(self, event):
  3272. self.SetStatus("Finding Centers for Wound Automatically...")
  3273. print("Asking for Wound Values")
  3274. dlg = wx.TextEntryDialog(
  3275. None,
  3276. "Which cells are wound (enter a comma-separated list of cell values)",
  3277. "Wound Values",
  3278. "#,#",
  3279. )
  3280. if dlg.ShowModal() == wx.ID_OK:
  3281. self.SetStatus("Running AutoCenterWound")
  3282. print("Run AutoCenterWound")
  3283. val = dlg.GetValue()
  3284. try:
  3285. woundVals = list(map(int, val.split(",")))
  3286. except:
  3287. print("Invalid Entry!!")
  3288. self.SetStatus("Ready")
  3289. return
  3290. if None not in woundVals:
  3291. self.wd.AutoCenterWound(woundVals)
  3292. self.wd.ColorPlot()
  3293. self.SetStatus("Ready")
  3294. def RunCalculationsCallback(self, event):
  3295. print("Run Calculations!")
  3296. # self.wd.TestCalculations()
  3297. self.SetStatus("Running Calculations")
  3298. for index in range(self.wd.length):
  3299. if self.wd.sparseList[index] is None:
  3300. print("frame", index, "is not initialized")
  3301. break
  3302. elif self.wd.sparseList[index].nnz == 0:
  3303. print("frame", index, "has no seeds")
  3304. break
  3305. elif self.wd.watershed[index].max() < 2:
  3306. print("frame", index, "only has background seeds")
  3307. break
  3308. if self.wd.woundCenters[index] is None:
  3309. s = (
  3310. "You must use Center Mode to define a"
  3311. + os.linesep
  3312. + "center for all initialized frames (failed on frame"
  3313. + str(index)
  3314. + ")!"
  3315. )
  3316. wx.MessageBox(s)
  3317. print(s)
  3318. return
  3319. self.wd.CollectAllStats(self.saveDir)
  3320. self.wd.PlotAreasAndPerimeters()
  3321. self.HandleMPLandWxKeyEvents("s")
  3322. self.SetStatus("Ready")
  3323. def RunCalculations2Callback(self, event):
  3324. print("Run Calculations (2)!")
  3325. # self.wd.TestCalculations()
  3326. self.SetStatus("Running Calculations (2)")
  3327. self.wd.RunCalculations2(self.saveDir)
  3328. self.SetStatus("Ready")
  3329. def CheckForMalformedRegionsCallback(self, event):
  3330. statusStr = "Checking for malformed regions"
  3331. print(statusStr)
  3332. self.SetStatus(statusStr)
  3333. s = SWHelpers.CheckForMalformedRegions(self.wd.watershed, usePrint=False)
  3334. print(s)
  3335. # Non-modal message:
  3336. dia = wx.Frame(None, -1, "Malformed Regions:")
  3337. wx.TextCtrl(
  3338. dia, -1, s, style=wx.TE_MULTILINE | wx.TE_READONLY
  3339. ) # add the text to the Dialog
  3340. dia.Show()
  3341. # wx.MessageBox(s,'Malformed Regions')
  3342. if s == "No malformed regions found!":
  3343. statusStr = (
  3344. "Attempting to make static and matched CellNetwork json files..."
  3345. )
  3346. print(statusStr)
  3347. self.SetStatus(statusStr)
  3348. allMatched = SWHelpers.MakeCellNetworkJsonFiles(
  3349. self.wd.watershed, self.saveDir
  3350. )
  3351. wx.MessageBox(
  3352. "Made matched and non-matched json files!"
  3353. if allMatched
  3354. else "Matching failed on some frames! Made json files anyway."
  3355. )
  3356. self.SetStatus("Ready")
  3357. def SetStatus(self, text):
  3358. # The 200 spaces ensure that any string will print completely after "Ready" (known problem on Linux)
  3359. self.StatusText.SetLabel(
  3360. "Status:\n "
  3361. + text.replace(os.linesep, "\n").replace("\n", "\n ")
  3362. + " " * 200
  3363. )
  3364. self.Update()
  3365. def HandleMPLMouseEvents(self, event):
  3366. if None not in [event.xdata, event.ydata]:
  3367. if self.mouseMode == "m":
  3368. pass
  3369. elif self.mouseMode == "a":
  3370. x, y = int(event.xdata + 0.5), int(event.ydata + 0.5)
  3371. if event.inaxes:
  3372. if event.button == 1:
  3373. print("Add", x, y)
  3374. self.wd.NewSeed([y, x])
  3375. # regionNum = out[int(event.ydata) , int(event.xdata)]
  3376. # place = np.where(inM==regionNum)
  3377. # wx,wy = place[0][0],place[1][0]
  3378. elif event.button == 3:
  3379. self.wd.DeleteSeedByRegion([y, x])
  3380. self.wd.ColorPlot() # ([x,y])
  3381. elif self.mouseMode == "e":
  3382. x, y = int(event.xdata + 0.5), int(event.ydata + 0.5)
  3383. if self.wd.watershed is not None and self.wd.woutline is not None:
  3384. if event.inaxes:
  3385. if event.button == 1:
  3386. self.wd.UpdateSelection([y, x])
  3387. self.wd.ColorPlot()
  3388. elif event.button == 3 and len(self.wd.selectionVals) == 1:
  3389. print("Add Point with value", self.wd.selectionVals[0])
  3390. print("At Location", [y, x])
  3391. self.wd.ExtraSeed([y, x], self.wd.selectionVals[0])
  3392. self.wd.ColorPlot()
  3393. elif self.mouseMode == "l":
  3394. x, y = int(event.xdata), int(event.ydata)
  3395. if self.wd.fig1.canvas.widgetlock.locked():
  3396. return
  3397. if event.inaxes and event.button == 1:
  3398. self.wd.lasso = PolyLasso(event.inaxes, self.wd.LassoCallback)
  3399. # acquire a lock on the widget drawing
  3400. self.wd.fig1.canvas.widgetlock(self.wd.lasso)
  3401. # self.wd.ColorPlot()
  3402. elif self.mouseMode == "x":
  3403. x, y = int(event.xdata + 0.5), int(event.ydata + 0.5)
  3404. if self.wd.watershed is not None and self.wd.woutline is not None:
  3405. if event.inaxes:
  3406. if event.button == 1:
  3407. self.wd.UpdateSelection([y, x])
  3408. self.wd.ColorPlot()
  3409. elif event.button == 3 and len(self.wd.selectionVals) == 1:
  3410. print(
  3411. "Switch all points with values:",
  3412. self.wd.selectionVals[0],
  3413. "and",
  3414. self.wd.watershed[self.wd.index][y, x],
  3415. )
  3416. self.wd.SwitchRegionValues(
  3417. self.wd.selectionVals[0],
  3418. self.wd.watershed[self.wd.index][y, x],
  3419. )
  3420. self.wd.ColorPlot()
  3421. self.wd.MapPlot()
  3422. elif self.mouseMode == "d":
  3423. x, y = int(event.xdata + 0.5), int(event.ydata + 0.5)
  3424. if self.wd.watershed is not None and self.wd.woutline is not None:
  3425. if event.inaxes:
  3426. if event.button == 1:
  3427. self.wd.UpdateSelection([y, x])
  3428. self.CellNumber.SetValue(self.wd.selectionVals[0])
  3429. self.wd.previousDrawPoint = None
  3430. self.wd.ColorPlot()
  3431. elif event.button == 2 and len(self.wd.selectionVals) == 1:
  3432. print("middle click...")
  3433. print("this should do 2 things:")
  3434. print(" 1: capture a second ")
  3435. print(
  3436. " 2: switch into a new sub-mode that lets me draw 2 lines"
  3437. )
  3438. # elif event.button == 3 and in new_special_mode:
  3439. #
  3440. elif event.button == 3 and len(self.wd.selectionVals) == 1:
  3441. if self.wd.previousDrawPoint is None:
  3442. self.wd.SetUndoPoint()
  3443. print("Start Drawing Seeds at Point", [y, x])
  3444. print("With value", self.wd.selectionVals[0])
  3445. self.wd.ExtraSeedLine(
  3446. [y, x], [y, x], self.wd.selectionVals[0]
  3447. )
  3448. else: # draw a line on the image
  3449. self.wd.ExtraSeedLine(
  3450. self.wd.previousDrawPoint,
  3451. [y, x],
  3452. self.wd.selectionVals[0],
  3453. )
  3454. self.wd.previousDrawPoint = [y, x]
  3455. self.wd.ColorPlot()
  3456. # self.wd.MapPlot()
  3457. elif self.mouseMode == "c":
  3458. x, y = event.xdata, event.ydata # Do not round x and y!
  3459. if self.wd.watershed is not None and self.wd.woutline is not None:
  3460. if event.inaxes:
  3461. if event.button == 1:
  3462. self.wd.woundCenters[self.wd.index] = [x, y]
  3463. elif event.button == 3:
  3464. for i in range(self.wd.index, self.wd.length):
  3465. self.wd.woundCenters[i] = [x, y]
  3466. self.wd.ColorPlot()
  3467. def OnKeyDownWx(self, event):
  3468. key = event.GetKeyCode()
  3469. self.shiftDown = event.ShiftDown()
  3470. if key == wx.WXK_DELETE:
  3471. ckey = "delete"
  3472. elif key == wx.WXK_BACK:
  3473. ckey = "backspace"
  3474. elif key == wx.WXK_UP:
  3475. return "up"
  3476. elif key == wx.WXK_DOWN:
  3477. return "down"
  3478. elif key == wx.WXK_LEFT:
  3479. return "left"
  3480. elif key == wx.WXK_RIGHT:
  3481. return "right"
  3482. elif key == wx.WXK_ADD:
  3483. return "+"
  3484. elif 0 <= key < 256:
  3485. ckey = chr(key).lower()
  3486. else:
  3487. ckey = None
  3488. if event.ControlDown():
  3489. self.HandleMPLandWxKeyEvents(ckey)
  3490. else:
  3491. event.Skip()
  3492. def OnKeyDownMPL(self, event):
  3493. if event.key == "shift":
  3494. self.shiftDown = True
  3495. else:
  3496. self.HandleMPLandWxKeyEvents(event.key.lower())
  3497. def OnKeyReleaseMPL(self, event):
  3498. if event.key == "shift":
  3499. self.shiftDown = False
  3500. def HandleMPLandWxKeyEvents(self, ckey):
  3501. if ckey == "o": # Open new file
  3502. self.SetStatus("Checking for File to Open")
  3503. f = wx.FileSelector(
  3504. "Select a tiff or gif file", default_filename=self.filename
  3505. )
  3506. if os.path.exists(f):
  3507. self.SetStatus("Opening File")
  3508. self.OnInit(f, setConnections=False)
  3509. print("Finished Loading")
  3510. elif ckey == "i": # Invert for watershed and MinFinder
  3511. self.SetStatus("Inverting Image")
  3512. self.wd.Invert() # NEEDS UNDO_RESET
  3513. self.InvertViewCheckbox.SetValue(self.wd.showInverted)
  3514. elif ckey == "g": # Gauss blur for MinFinder
  3515. self.SetStatus("Checking for Gaussian")
  3516. dlg = wx.TextEntryDialog(None, "Sigma Value?", "Gauss Average Data", "4.0")
  3517. if dlg.ShowModal() == wx.ID_OK:
  3518. self.SetStatus("Running Gauss Filter")
  3519. val = dlg.GetValue()
  3520. try:
  3521. sigma = float(val)
  3522. except:
  3523. sigma = None
  3524. print("Invalid Number!!")
  3525. if sigma != None:
  3526. self.wd.Gauss(sigma)
  3527. # self.wd.DrawOrigImage()
  3528. doWater = False
  3529. if self.wd.sparseList[self.wd.index] is None:
  3530. doWater = True
  3531. elif self.wd.sparseList[self.wd.index].nnz == 0:
  3532. doWater = True
  3533. else:
  3534. self.SetStatus("Checking On Update Seeds")
  3535. dlg = wx.MessageDialog(
  3536. self,
  3537. "Are you sure you want revert any manual seed changes?",
  3538. "Revert Seeds Using Minima?",
  3539. wx.OK | wx.ICON_INFORMATION | wx.CANCEL,
  3540. )
  3541. if dlg.ShowModal() == wx.ID_OK:
  3542. doWater = True
  3543. if doWater:
  3544. self.SetStatus("Running Watershed")
  3545. self.wd.UpdateSeeds(force=True)
  3546. print("Run Watershed")
  3547. self.wd.Watershed()
  3548. self.wd.ColorPlot()
  3549. self.wd.MapPlot()
  3550. print("Watershed Done")
  3551. else:
  3552. if self.wd.sparseList[self.wd.index] is None:
  3553. sh = self.wd.shape[1:]
  3554. self.wd.sparseList[self.wd.index] = scipy.sparse.lil_matrix(
  3555. sh, dtype=np.uint16
  3556. )
  3557. # I guess I could change the dtype later if I need to...
  3558. self.wd.seedSelections[self.wd.index] = scipy.sparse.lil_matrix(
  3559. sh, dtype=np.bool
  3560. )
  3561. elif ckey == "r": # Reset all data
  3562. self.SetStatus("Resetting All Filters")
  3563. print("Reset All Filters on Figures 1 and 2")
  3564. self.wd.ResetData() # NEEDS UNDO_RESET
  3565. # self.wd.DrawOrigImage()
  3566. self.wd.ColorPlot()
  3567. elif ckey == "b": # Background for watershed
  3568. if not STEAL_B_KEY:
  3569. # Can't Undo
  3570. self.SetStatus("Checking for Background Subtraction")
  3571. dlg = wx.TextEntryDialog(
  3572. None, "Sigma Value?", "Background Subtraction", "64.0"
  3573. )
  3574. if dlg.ShowModal() == wx.ID_OK:
  3575. self.SetStatus("Running Background Subtraction")
  3576. val = dlg.GetValue()
  3577. try:
  3578. sigma = float(val)
  3579. except:
  3580. sigma = None
  3581. print("Invalid Number!!")
  3582. if sigma != None:
  3583. self.wd.BGSubtract(sigma) # NEEDS UNDO_RESET
  3584. self.wd.ColorPlot()
  3585. else:
  3586. self.HandleMPLandWxKeyEvents("n")
  3587. if self.wd.index > 0:
  3588. print("Copy seeds from previous frame")
  3589. self.SetStatus("Copying seeds from previous frame")
  3590. self.wd.CopySeedsFromPrevious() # NEEDS UNDO
  3591. self.wd.ColorPlot()
  3592. self.wd.MapPlot()
  3593. elif ckey == "h": # Sharpen for watershed
  3594. self.SetStatus("Checking for Sharpen")
  3595. dlg = wx.TextEntryDialog(
  3596. None,
  3597. "Two Comma-separated values for Sharpen Filter?",
  3598. "Sharpen Data",
  3599. "2.0,6.0",
  3600. )
  3601. if dlg.ShowModal() == wx.ID_OK:
  3602. self.SetStatus("Running Sharpen Filter")
  3603. val = dlg.GetValue()
  3604. try:
  3605. v1, v2 = val.split(",")
  3606. s1 = float(v1)
  3607. s2 = float(v2)
  3608. except:
  3609. s1 = None
  3610. s2 = None
  3611. print("Invalid Number!!")
  3612. if None not in [s1, s2]:
  3613. self.wd.Sharpen(s1, s2) # NEEDS UNDO_RESET
  3614. self.wd.ColorPlot()
  3615. elif ckey == "k": # Median Filter for watershed
  3616. self.SetStatus("Checking for Median")
  3617. dlg = wx.TextEntryDialog(
  3618. None, "Median Filter Size?", "Median Filter Data", "3"
  3619. )
  3620. if dlg.ShowModal() == wx.ID_OK:
  3621. self.SetStatus("Running Median Filter")
  3622. val = dlg.GetValue()
  3623. try:
  3624. v = float(val)
  3625. except:
  3626. v = None
  3627. print("Invalid Number!!")
  3628. if v is not None:
  3629. self.wd.Median(v) # NEEDS UNDO_RESET
  3630. self.wd.ColorPlot()
  3631. elif ckey == "t": # Toggle color overlays for figure 2
  3632. self.wd.ToggleOverlaysVisible()
  3633. self.ToggleViewCheckbox.SetValue(self.wd.overlayVisible)
  3634. elif ckey == "s": # Save
  3635. self.SetStatus("Checking for Save")
  3636. d = wx.DirSelector(
  3637. "Optionally Pick Directory Where Seed Info is Stored", self.saveDir
  3638. )
  3639. if len(d) > 0:
  3640. self.SetStatus("Saving")
  3641. self.wd.notes[self.wd.index] = self.NotesTextBox.GetValue()
  3642. self.saveDir = d
  3643. self.wd.Save(d)
  3644. print("Finished Saving")
  3645. else:
  3646. print("Save cancelled")
  3647. elif ckey == "u": # Undo last manual seed change
  3648. self.SetStatus("Undoing changes")
  3649. print("Undo")
  3650. self.wd.Undo()
  3651. self.wd.UpdateSeeds()
  3652. self.wd.ColorPlot()
  3653. elif ckey == "n": # Next frame
  3654. ind = self.wd.index + 1
  3655. if ind < self.wd.length:
  3656. if self.wd.sparseList[ind] is None:
  3657. self.SetStatus("Moving to Frame " + str(ind) + " (with Watershed)")
  3658. else:
  3659. self.SetStatus("Moving to Frame " + str(ind))
  3660. self.wd.notes[self.wd.index] = self.NotesTextBox.GetValue()
  3661. self.wd.NextFrame()
  3662. self.FrameNumber.SetValue(self.wd.index)
  3663. self.UpdateFrameLabelText()
  3664. if self.wd.notes[self.wd.index] is None:
  3665. self.wd.notes[self.wd.index] = ""
  3666. self.NotesTextBox.SetValue(self.wd.notes[self.wd.index])
  3667. print("Moved to Frame", self.wd.index)
  3668. else:
  3669. print("No more frames!")
  3670. elif ckey == "f": # Force recreate from previous centroids
  3671. # Can Undo
  3672. if self.wd.index > 0:
  3673. if self.shiftDown:
  3674. print("Copy seeds from previous frame")
  3675. self.SetStatus("Copying seeds from previous frame")
  3676. self.wd.CopySeedsFromPrevious() # NEEDS UNDO
  3677. else:
  3678. print("Remake seeds from previous frame")
  3679. self.SetStatus("Remaking seeds from last frame")
  3680. self.wd.MakeSeedsFromPrevious() # NEEDS UNDO
  3681. self.wd.ColorPlot()
  3682. self.wd.MapPlot()
  3683. elif ckey == "p": # Previous frame
  3684. if self.shiftDown:
  3685. ind = self.wd.lastFrameVisited
  3686. else:
  3687. ind = self.wd.index - 1
  3688. if ind >= 0:
  3689. self.SetStatus("Moving to Frame " + str(ind))
  3690. self.wd.notes[self.wd.index] = self.NotesTextBox.GetValue()
  3691. self.wd.MoveToFrame(ind)
  3692. self.FrameNumber.SetValue(self.wd.index)
  3693. self.UpdateFrameLabelText()
  3694. if self.wd.notes[self.wd.index] is None:
  3695. self.wd.notes[self.wd.index] = ""
  3696. self.NotesTextBox.SetValue(self.wd.notes[self.wd.index])
  3697. print("Moved to Frame", self.wd.index)
  3698. else:
  3699. print("Already at the beginning!")
  3700. # OpenCV usage deprecated... never that great...
  3701. # C key stolen for center/calculate mode...
  3702. # elif ckey=='c': # Toggle cv on or off
  3703. # if HAS_CV:
  3704. # print('Use the faster OpenCV watershed algorithm.')
  3705. # self.SetStatus('Run Watershed (OpenCV algorithm)')
  3706. # self.wd.Watershed('OpenCV') # Ignore undo...
  3707. # self.wd.ColorPlot()
  3708. # self.wd.MapPlot()
  3709. # print('Watershed Done')
  3710. # else:
  3711. # print('OpenCV is not installed')
  3712. # print('Use PyMorph instead!')
  3713. elif ckey == "w": # Run watershed
  3714. print("Run Watershed (PyMorph algorithm)")
  3715. self.SetStatus("Running Watershed (PyMorph algorithm)")
  3716. self.wd.Watershed("PyMorph") # Ignore undo...
  3717. self.wd.ColorPlot()
  3718. self.wd.MapPlot()
  3719. print("Watershed Done")
  3720. elif ckey == "y": # Run Dummy watershed for bad frames...
  3721. print("Run Dummy Watershed for Bad Frames")
  3722. self.SetStatus("Running Dummy Watershed")
  3723. self.wd.Watershed("Dummy") # Ignore undo...
  3724. self.wd.ColorPlot()
  3725. self.wd.MapPlot()
  3726. elif ckey in [
  3727. "up",
  3728. "down",
  3729. "left",
  3730. "right",
  3731. "=",
  3732. "+",
  3733. "'",
  3734. '"',
  3735. "[",
  3736. "]",
  3737. "{",
  3738. "}",
  3739. ]:
  3740. self.SetStatus("Moving Seeds")
  3741. u = [0, 0]
  3742. if ckey in ["up", "=", "+"]:
  3743. u = [-1, 0]
  3744. elif ckey in ["down", "'", '"']:
  3745. u = [1, 0]
  3746. elif ckey in ["left", "[", "{"]:
  3747. u = [0, -1]
  3748. elif ckey in ["right", "]", "}"]:
  3749. u = [0, 1]
  3750. ind = self.wd.index
  3751. wh = np.where(self.wd.seedSelections[self.wd.index].toarray())
  3752. oldSA = self.wd.seedArray[wh]
  3753. sel = self.wd.seedSelections[self.wd.index].toarray()
  3754. self.wd.seedArray[wh] = 0
  3755. sel[wh] = 0
  3756. wh = (wh[0] + u[0], wh[1] + u[1])
  3757. np.clip(
  3758. wh[0], 0, self.wd.shape[1]
  3759. ) # This is an ok solution, but there is probably a better one...
  3760. np.clip(wh[1], 0, self.wd.shape[2])
  3761. self.wd.seedArray[wh] = oldSA
  3762. sel[wh] = 1
  3763. self.wd.seedSelections[self.wd.index] = scipy.sparse.lil_matrix(
  3764. sel, dtype=np.bool
  3765. )
  3766. self.wd.sparseList[self.wd.index] = scipy.sparse.lil_matrix(
  3767. self.wd.seedArray, dtype=np.uint16
  3768. )
  3769. # I broke this old behavior for nudge for now...
  3770. # I think the right way to do it is basically a cut and paste for an overlay array...or something...
  3771. # for i in range(len(self.wd.seedList[ind])):
  3772. # if self.wd.seedSelections[ind][i]:
  3773. # self.wd.seedList[ind][i][0] = self.wd.seedList[ind][i][0]+u[0]
  3774. # self.wd.seedList[ind][i][1] = self.wd.seedList[ind][i][1]+u[1]
  3775. self.wd.UpdateSeeds()
  3776. self.wd.ColorPlot()
  3777. elif ckey in ["delete", "backspace"]: # Delete After Lasso
  3778. # Need to implement the modes like point_mode
  3779. # and region_select_mode
  3780. # then d should delete the seeds, etc...
  3781. # maybe m for merge, etc...
  3782. # Should I also apply changes directly to the watershed map if possible?
  3783. # I think that could get really confusing really quickly...
  3784. if self.wd.point_mode:
  3785. if self.shiftDown:
  3786. print("Deleting Outside Lasso Now...")
  3787. self.SetStatus("Deleting Unselected Points")
  3788. print(self.wd.sparseList[self.wd.index].nnz)
  3789. print(self.wd.oldSparseList.nnz)
  3790. self.wd.DeleteSelectedSeeds(invertSelections=True) # NEEDS UNDO
  3791. print(self.wd.sparseList[self.wd.index].nnz)
  3792. print(self.wd.oldSparseList.nnz)
  3793. self.wd.ColorPlot()
  3794. print("Finished Deleting")
  3795. else:
  3796. print("Deleting Now...")
  3797. self.SetStatus("Deleting Selected Points")
  3798. print(self.wd.sparseList[self.wd.index].nnz)
  3799. print(self.wd.oldSparseList.nnz)
  3800. self.wd.DeleteSelectedSeeds() # NEEDS UNDO
  3801. print(self.wd.sparseList[self.wd.index].nnz)
  3802. print(self.wd.oldSparseList.nnz)
  3803. self.wd.ColorPlot()
  3804. print("Finished Deleting")
  3805. elif ckey == "m":
  3806. if self.wd.watershed is not None and self.wd.woutline is not None:
  3807. print("Change to Move Mode (Default)")
  3808. self.mouseMode = "m"
  3809. self.MouseModeRadioBox.SetSelection(0)
  3810. self.MouseModeHelpText.SetLabel(mouseModeHelpTxt[0])
  3811. self.wd.showWoundCenters = False
  3812. elif ckey == "a": # Activate Add/Delete (by region) mode
  3813. if self.wd.watershed is not None and self.wd.woutline is not None:
  3814. print("Change to Add/Delete Mode")
  3815. self.mouseMode = "a"
  3816. self.MouseModeRadioBox.SetSelection(1)
  3817. self.MouseModeHelpText.SetLabel(mouseModeHelpTxt[1])
  3818. self.wd.showWoundCenters = False
  3819. elif ckey in ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]:
  3820. self.wd.pointSize = int(ckey)
  3821. elif ckey == "l": # Activate Extra Seed mode
  3822. print("Change to Lasso Mode")
  3823. self.mouseMode = "l"
  3824. self.MouseModeRadioBox.SetSelection(2)
  3825. self.MouseModeHelpText.SetLabel(mouseModeHelpTxt[2])
  3826. self.wd.point_mode = True
  3827. self.wd.showWoundCenters = False
  3828. self.wd.ColorPlot()
  3829. elif ckey == "e": # Activate Extra Seed mode
  3830. if self.wd.watershed is not None and self.wd.woutline is not None:
  3831. print("Change to Extras (Highlight) Mode")
  3832. self.mouseMode = "e"
  3833. self.MouseModeRadioBox.SetSelection(3)
  3834. self.MouseModeHelpText.SetLabel(mouseModeHelpTxt[3])
  3835. self.wd.point_mode = False
  3836. self.wd.showWoundCenters = False
  3837. self.wd.ColorPlot()
  3838. elif ckey == "x": # Activate Switch mode
  3839. if self.wd.watershed is not None and self.wd.woutline is not None:
  3840. print("Change to Switch Mode")
  3841. self.mouseMode = "x"
  3842. self.MouseModeRadioBox.SetSelection(4)
  3843. self.MouseModeHelpText.SetLabel(mouseModeHelpTxt[4])
  3844. self.wd.point_mode = False
  3845. self.wd.showWoundCenters = False
  3846. self.wd.ColorPlot()
  3847. elif ckey == "d": # Draw Mode
  3848. if self.wd.watershed is not None and self.wd.woutline is not None:
  3849. print("Change to Draw Mode")
  3850. self.mouseMode = "d"
  3851. self.MouseModeRadioBox.SetSelection(5)
  3852. self.MouseModeHelpText.SetLabel(mouseModeHelpTxt[5])
  3853. self.wd.point_mode = False
  3854. self.wd.previousDrawPoint = None
  3855. self.wd.showWoundCenters = False
  3856. self.wd.ColorPlot()
  3857. elif ckey == "c":
  3858. print("Change to Center Select Mode--For Postprocessing")
  3859. self.mouseMode = "c"
  3860. self.MouseModeRadioBox.SetSelection(6)
  3861. self.MouseModeHelpText.SetLabel(mouseModeHelpTxt[6])
  3862. self.wd.showWoundCenters = True
  3863. self.wd.ColorPlot()
  3864. elif ckey == "j":
  3865. if self.shiftDown and USE_DEBUG_PRINT: # Aka, if it's a developer...
  3866. te = wx.TextEntryDialog(
  3867. None,
  3868. "Inject Code... very dangerous, but fun!",
  3869. style=wx.TE_MULTILINE | wx.OK | wx.CANCEL,
  3870. )
  3871. if te.ShowModal() == wx.ID_OK:
  3872. exec(te.GetValue())
  3873. else:
  3874. if self.wd.point_mode:
  3875. if self.wd.watershed is not None and self.wd.woutline is not None:
  3876. print("Join Seeds to be same type")
  3877. self.SetStatus("Joining Seeds")
  3878. self.wd.MergeSelectedSeeds() # NEEDS UNDO
  3879. self.wd.ColorPlot()
  3880. elif ckey == "v":
  3881. if not self.wd.point_mode:
  3882. if self.wd.watershed is not None and self.wd.woutline is not None:
  3883. print("Change the value of a watershed region")
  3884. dlg = wx.TextEntryDialog(
  3885. None, "New Value?", "Pick a new value for the region.", ""
  3886. )
  3887. if dlg.ShowModal() == wx.ID_OK:
  3888. newVal = int(dlg.GetValue())
  3889. self.wd.ChangeRegionValue(
  3890. self.wd.selectionVals[0], newVal
  3891. ) # NEEDS UNDO
  3892. self.wd.ColorPlot()
  3893. self.wd.MapPlot()
  3894. elif ckey == "q": # Quit
  3895. dlg = wx.MessageDialog(
  3896. self,
  3897. "Are you sure you want to Quit?",
  3898. "Exit?",
  3899. wx.OK | wx.ICON_INFORMATION | wx.CANCEL,
  3900. )
  3901. if dlg.ShowModal() == wx.ID_OK:
  3902. wx.Exit()
  3903. if ckey not in ["d", "t", "i"]:
  3904. self.wd.previousDrawPoint = None
  3905. self.SetStatus("Ready")
  3906. class SegmenterApp(wx.App):
  3907. def OnInit(self):
  3908. wx.InitAllImageHandlers()
  3909. self.frame = SegmenterFrame(None, -1, "")
  3910. self.SetTopWindow(self.frame)
  3911. self.frame.Show()
  3912. return 1
  3913. def InitMPL():
  3914. # Initialize the two figures in reverse order and disable keyboard navigation keys
  3915. fig2 = plt.figure(2) # initialize this first so it stays at the back...
  3916. fig1 = plt.figure(1)
  3917. ax1 = fig1.add_subplot(111)
  3918. ax2 = fig2.add_subplot(111)
  3919. for fig, ax in [(fig1, ax1), (fig2, ax2)]:
  3920. if fig == fig2 and DONT_PANIC:
  3921. fig.title("Don't Panic!!!")
  3922. fig.canvas.draw()
  3923. callbacks_to_remove = [i
  3924. for i in fig.canvas.callbacks.callbacks
  3925. if i == "key_press_event"
  3926. ]
  3927. for i in callbacks_to_remove:
  3928. fig.canvas.mpl_disconnect(
  3929. list(fig.canvas.callbacks.callbacks[i].keys())[0]
  3930. )
  3931. return fig1, ax1, fig2, ax2
  3932. def InitializeMPL():
  3933. plt.ion()
  3934. def start_sws():
  3935. InitializeMPL()
  3936. app = SegmenterApp(0)
  3937. if sys.platform == "darwin":
  3938. # Workaround for Mac bug
  3939. # Create and immediately destroy a file dialog to prevent a crash
  3940. dlg = wx.FileDialog(None, "")
  3941. wx.FutureCall(1, dlg.Destroy)
  3942. dlg.ShowModal()
  3943. f = (
  3944. wx.FileSelector("Select a tiff or gif file")
  3945. if len(sys.argv) <= 1
  3946. else " ".join(sys.argv[1:])
  3947. if CAT_SPACED_ARGS
  3948. else sys.argv[1]
  3949. )
  3950. if not os.path.exists(f):
  3951. print("The specified path does not exist.")
  3952. print(f)
  3953. exit()
  3954. d = "" if CAT_SPACED_ARGS or len(sys.argv) <= 2 else sys.argv[2]
  3955. fig1, ax1, fig2, ax2 = InitMPL()
  3956. app.frame.OnInit(filename=f, saveDir=d, fig1=fig1, ax1=ax1, fig2=fig2, ax2=ax2)
  3957. app.MainLoop()

SeedWaterSegmenter.py at commit 45bd4bf, under other · at the source

Overview

  1. Department of Molecular Biomedical Sciences, College of Veterinary Medicine, North Carolina State University, Raleigh, NC, USA
Institutions: North Carolina State University (United States)
Journal: Bio-protocol, volume 16, issue 11, article e5711
Dates: received 18 February 2026; accepted 30 April 2026; published online 5 June 2026
Type: Methods article · Language: English
License: CC BY
Identifiers: DOI 10.21769/bioprotoc.5711 · PMID 42305133 · PMCID PMC13265412 · OpenAlex W7161256386
Open access: diamond, a free copy (OpenAlex)
Status: code verified
Categories: mouse (organism), developmental (subfield)
Methods: Statistics, fMRI & imaging
Keywords: Neural tube closure, Cranial tissues, Mouse development, Quantitative image analysis, Segmentation
Topic: Nerve injury and regeneration (Cellular and Molecular Neuroscience, Neuroscience), according to OpenAlex
Citations: not cited yet (Europe PMC); 33 references in the paper

Abstract

Neural tube closure is a critical process that transforms the neural plate, an open epithelial tissue, into the closed tube that serves as the structural basis of the central nervous system. Defects in this process are among the most common and severe developmental diseases in the human population, with failures in cranial closure accounting for approximately one-third of total defects. However, the cell and tissue mechanisms that drive cranial closure remain opaque relative to the better studied process of spinal closure, in large part due to the unique challenges in characterizing cranial tissues. Here, we present protocols for quantifying cell dynamics and tissue-level remodeling events that enable highly spatiotemporally resolved investigations of the causes of cranial closure defects in mouse embryos. These include brightfield morphometric approaches, fluorescent staining and confocal imaging, and quantitative pipelines to analyze these image-based datasets. At the conclusion of these approaches, users will be able to quantify several parameters of overall tissue shape in the cranial neural tissues and produce rich quantitative datasets about cell-level parameters, particularly apical cell area. These can be used to identify correlative and causative differences between mutants and control embryos. Given their flexibility, many of these approaches can be generalized to other tissue morphogenetic contexts.

Key features

• Provides flexible and quantitative pipelines for cell and tissue-scale morphometrics, which can be expanded to many morphogenetic problems.

• Presents robust mounting and imaging methods for cranial tissues.

• Allows assessing the role of cellular-scale remodeling events in deforming tissues during cranial closure and how these are changed in mutants.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repository

Its files are read in the Code ↔ Paper reader above, with 1 match between paragraphs and lines of code.

davidmashburn/SeedWaterSegmenter

License: other
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 45bd4bf22184f3daae3add27f7b2d2136c1573de, 31 March 2022
Languages: Python (17), Shell (2)
Size: 61 files, 19 scripts
Software Heritage: not archived
Found in: the text, “Data analysis”
Holds: README, license file, environment (requirements.txt, setup.py), documentation
Not found: CITATION.cff, tests, continuous integration
Tools: Matplotlib (8 files), NumPy (8 files), SciPy (3 files)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
21 files

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 19 scripts, each with its path and the digest of its content;
  • 1 match between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 2 authors, 5 keywords, 31 references.

Cite

This paper

Borys, K. A., & Brooks, E. R. (2026). Quantitative Analysis of Cell and Tissue Shape During Mouse Cranial Neural Tube Closure. Bio-protocol, 16(11), e5711. https://doi.org/10.21769/bioprotoc.5711

BibTeX

@article{borys2026quantitative,
author = {Borys, Kristina A and Brooks, Eric R},
title = {{Quantitative Analysis of Cell and Tissue Shape During Mouse Cranial Neural Tube Closure}},
journal = {Bio-protocol},
year = {2026},
month = jun,
volume = {16},
number = {11},
pages = {e5711},
publisher = {Bio-protocol, LLC},
issn = {2331-8325},
doi = {10.21769/bioprotoc.5711},
url = {https://doi.org/10.21769/bioprotoc.5711},
pmid = {42305133},
pmcid = {PMC13265412}
}

RIS

TY - JOUR
AU - Borys, Kristina A
AU - Brooks, Eric R
TI - Quantitative Analysis of Cell and Tissue Shape During Mouse Cranial Neural Tube Closure
T2 - Bio-protocol
J2 - Bio Protoc
PY - 2026
DA - 2026/06/05
VL - 16
IS - 11
SP - e5711
SN - 2331-8325
PB - Bio-protocol, LLC
DO - 10.21769/bioprotoc.5711
UR - https://doi.org/10.21769/bioprotoc.5711
LA - en
ER -

CSL-JSON

{
"id": "10.21769/bioprotoc.5711",
"type": "article-journal",
"title": "Quantitative Analysis of Cell and Tissue Shape During Mouse Cranial Neural Tube Closure",
"container-title": "Bio-protocol",
"author": [
{
"family": "Borys",
"given": "Kristina A"
},
{
"family": "Brooks",
"given": "Eric R"
}
],
"container-title-short": "Bio Protoc",
"volume": "16",
"issue": "11",
"page": "e5711",
"DOI": "10.21769/bioprotoc.5711",
"PMID": "42305133",
"PMCID": "PMC13265412",
"ISSN": "2331-8325",
"publisher": "Bio-protocol, LLC",
"URL": "https://doi.org/10.21769/bioprotoc.5711",
"language": "en",
"issued": {
"date-parts": [
[
2026,
6,
5
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1186/s13321-026-01177-7 [code]
A pipeline for developing AI-driven models to predict molecular initiating events: a case study on neural tube defects.
Journal: Journal of cheminformatics
In common: SciPy, Matplotlib, NumPy, 2 references
[2] doi:10.1038/s41467-026-73505-2 [code]
Light- and chemical-induced ciliary signaling governs dorsal/ventral regionalization of human telencephalic organoids.
Journal: Nature communications
In common: 3 references
[3] doi:10.1126/sciadv.aed5548 [code]
In situ proteomics unveils specialized domains for extrasynaptic signaling on neuronal cilia.
Journal: Science advances
In common: SciPy, Matplotlib, NumPy, mouse, 1 reference
[4] doi:10.1126/sciadv.adu3955 [code]
Defective EV-mediated transport of SHH alters neural fate specification in EPM1 epilepsy.
Journal: Science advances
In common: SciPy, Matplotlib, NumPy, 2 references
[5] doi:10.7554/elife.106347 [code]
Esr1-dependent signaling and transcriptional maturation in the medial preoptic area of the hypothalamus shape the development of mating behavior during adolescence.
Journal: eLife
In common: SciPy, Matplotlib, NumPy, developmental, mouse, 1 reference
[6] doi:10.1002/advs.202524341 [code]
Temporal Interference Stimulation Enhances Neural Regeneration.
Journal: Advanced science (Weinheim, Baden-Wurttemberg, Germany)
In common: SciPy, Matplotlib, NumPy, developmental, mouse, 1 reference
[7] doi:10.1002/advs.202522762 [code]
Enhancing Maturation of Human Neuromuscular Organoids via Electrical Stimulation.
Journal: Advanced science (Weinheim, Baden-Wurttemberg, Germany)
In common: SciPy, Matplotlib, NumPy, developmental, 1 reference
[8] doi:10.1038/s41467-026-74320-5 [code]
Spatial architecture of autism pathogenesis reveals mosaic structural disarray during early development.
Journal: Nature communications
In common: SciPy, Matplotlib, NumPy, developmental, 1 reference
[9] doi:10.21769/bioprotoc.5708 [code]
Measuring Electrophysiological Activity in Acute Brain Slices, Spheroids, and Organoids Using 3D High-Density Multielectrode Arrays.
Journal: Bio-protocol
In common: SciPy, Matplotlib, NumPy, 1 reference
[10] doi:10.21769/bioprotoc.5616 [code]
Reconstruction of Axonal Projections of Single Neurons Using PointTree.
Journal: Bio-protocol
In common: SciPy, Matplotlib, NumPy, 1 reference

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.