dclab 0.67.0__cp314-cp314t-macosx_10_13_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of dclab might be problematic. Click here for more details.

Files changed (142) hide show
  1. dclab/__init__.py +41 -0
  2. dclab/_version.py +34 -0
  3. dclab/cached.py +97 -0
  4. dclab/cli/__init__.py +10 -0
  5. dclab/cli/common.py +237 -0
  6. dclab/cli/task_compress.py +126 -0
  7. dclab/cli/task_condense.py +223 -0
  8. dclab/cli/task_join.py +229 -0
  9. dclab/cli/task_repack.py +98 -0
  10. dclab/cli/task_split.py +154 -0
  11. dclab/cli/task_tdms2rtdc.py +186 -0
  12. dclab/cli/task_verify_dataset.py +75 -0
  13. dclab/definitions/__init__.py +79 -0
  14. dclab/definitions/feat_const.py +202 -0
  15. dclab/definitions/feat_logic.py +182 -0
  16. dclab/definitions/meta_const.py +252 -0
  17. dclab/definitions/meta_logic.py +111 -0
  18. dclab/definitions/meta_parse.py +94 -0
  19. dclab/downsampling.cpython-314t-darwin.so +0 -0
  20. dclab/downsampling.pyx +230 -0
  21. dclab/external/__init__.py +4 -0
  22. dclab/external/packaging/LICENSE +3 -0
  23. dclab/external/packaging/LICENSE.APACHE +177 -0
  24. dclab/external/packaging/LICENSE.BSD +23 -0
  25. dclab/external/packaging/__init__.py +6 -0
  26. dclab/external/packaging/_structures.py +61 -0
  27. dclab/external/packaging/version.py +505 -0
  28. dclab/external/skimage/LICENSE +28 -0
  29. dclab/external/skimage/__init__.py +2 -0
  30. dclab/external/skimage/_find_contours.py +216 -0
  31. dclab/external/skimage/_find_contours_cy.cpython-314t-darwin.so +0 -0
  32. dclab/external/skimage/_find_contours_cy.pyx +188 -0
  33. dclab/external/skimage/_pnpoly.cpython-314t-darwin.so +0 -0
  34. dclab/external/skimage/_pnpoly.pyx +99 -0
  35. dclab/external/skimage/_shared/__init__.py +1 -0
  36. dclab/external/skimage/_shared/geometry.cpython-314t-darwin.so +0 -0
  37. dclab/external/skimage/_shared/geometry.pxd +6 -0
  38. dclab/external/skimage/_shared/geometry.pyx +55 -0
  39. dclab/external/skimage/measure.py +7 -0
  40. dclab/external/skimage/pnpoly.py +53 -0
  41. dclab/external/statsmodels/LICENSE +35 -0
  42. dclab/external/statsmodels/__init__.py +6 -0
  43. dclab/external/statsmodels/nonparametric/__init__.py +1 -0
  44. dclab/external/statsmodels/nonparametric/_kernel_base.py +203 -0
  45. dclab/external/statsmodels/nonparametric/kernel_density.py +165 -0
  46. dclab/external/statsmodels/nonparametric/kernels.py +36 -0
  47. dclab/features/__init__.py +9 -0
  48. dclab/features/bright.py +81 -0
  49. dclab/features/bright_bc.py +93 -0
  50. dclab/features/bright_perc.py +63 -0
  51. dclab/features/contour.py +161 -0
  52. dclab/features/emodulus/__init__.py +339 -0
  53. dclab/features/emodulus/load.py +252 -0
  54. dclab/features/emodulus/lut_HE-2D-FEM-22.txt +16432 -0
  55. dclab/features/emodulus/lut_HE-3D-FEM-22.txt +1276 -0
  56. dclab/features/emodulus/lut_LE-2D-FEM-19.txt +13082 -0
  57. dclab/features/emodulus/pxcorr.py +135 -0
  58. dclab/features/emodulus/scale_linear.py +247 -0
  59. dclab/features/emodulus/viscosity.py +260 -0
  60. dclab/features/fl_crosstalk.py +95 -0
  61. dclab/features/inert_ratio.py +377 -0
  62. dclab/features/volume.py +242 -0
  63. dclab/http_utils.py +322 -0
  64. dclab/isoelastics/__init__.py +468 -0
  65. dclab/isoelastics/iso_HE-2D-FEM-22-area_um-deform.txt +2440 -0
  66. dclab/isoelastics/iso_HE-2D-FEM-22-volume-deform.txt +2635 -0
  67. dclab/isoelastics/iso_HE-3D-FEM-22-area_um-deform.txt +1930 -0
  68. dclab/isoelastics/iso_HE-3D-FEM-22-volume-deform.txt +2221 -0
  69. dclab/isoelastics/iso_LE-2D-FEM-19-area_um-deform.txt +2151 -0
  70. dclab/isoelastics/iso_LE-2D-FEM-19-volume-deform.txt +2250 -0
  71. dclab/isoelastics/iso_LE-2D-ana-18-area_um-deform.txt +1266 -0
  72. dclab/kde/__init__.py +1 -0
  73. dclab/kde/base.py +459 -0
  74. dclab/kde/contours.py +222 -0
  75. dclab/kde/methods.py +313 -0
  76. dclab/kde_contours.py +10 -0
  77. dclab/kde_methods.py +11 -0
  78. dclab/lme4/__init__.py +5 -0
  79. dclab/lme4/lme4_template.R +94 -0
  80. dclab/lme4/rsetup.py +204 -0
  81. dclab/lme4/wrapr.py +386 -0
  82. dclab/polygon_filter.py +398 -0
  83. dclab/rtdc_dataset/__init__.py +15 -0
  84. dclab/rtdc_dataset/check.py +902 -0
  85. dclab/rtdc_dataset/config.py +533 -0
  86. dclab/rtdc_dataset/copier.py +353 -0
  87. dclab/rtdc_dataset/core.py +896 -0
  88. dclab/rtdc_dataset/export.py +867 -0
  89. dclab/rtdc_dataset/feat_anc_core/__init__.py +24 -0
  90. dclab/rtdc_dataset/feat_anc_core/af_basic.py +75 -0
  91. dclab/rtdc_dataset/feat_anc_core/af_emodulus.py +160 -0
  92. dclab/rtdc_dataset/feat_anc_core/af_fl_max_ctc.py +133 -0
  93. dclab/rtdc_dataset/feat_anc_core/af_image_contour.py +113 -0
  94. dclab/rtdc_dataset/feat_anc_core/af_ml_class.py +102 -0
  95. dclab/rtdc_dataset/feat_anc_core/ancillary_feature.py +320 -0
  96. dclab/rtdc_dataset/feat_anc_ml/__init__.py +32 -0
  97. dclab/rtdc_dataset/feat_anc_plugin/__init__.py +3 -0
  98. dclab/rtdc_dataset/feat_anc_plugin/plugin_feature.py +329 -0
  99. dclab/rtdc_dataset/feat_basin.py +762 -0
  100. dclab/rtdc_dataset/feat_temp.py +102 -0
  101. dclab/rtdc_dataset/filter.py +263 -0
  102. dclab/rtdc_dataset/fmt_dcor/__init__.py +7 -0
  103. dclab/rtdc_dataset/fmt_dcor/access_token.py +52 -0
  104. dclab/rtdc_dataset/fmt_dcor/api.py +173 -0
  105. dclab/rtdc_dataset/fmt_dcor/base.py +299 -0
  106. dclab/rtdc_dataset/fmt_dcor/basin.py +73 -0
  107. dclab/rtdc_dataset/fmt_dcor/logs.py +26 -0
  108. dclab/rtdc_dataset/fmt_dcor/tables.py +66 -0
  109. dclab/rtdc_dataset/fmt_dict.py +103 -0
  110. dclab/rtdc_dataset/fmt_hdf5/__init__.py +6 -0
  111. dclab/rtdc_dataset/fmt_hdf5/base.py +192 -0
  112. dclab/rtdc_dataset/fmt_hdf5/basin.py +30 -0
  113. dclab/rtdc_dataset/fmt_hdf5/events.py +276 -0
  114. dclab/rtdc_dataset/fmt_hdf5/feat_defect.py +164 -0
  115. dclab/rtdc_dataset/fmt_hdf5/logs.py +33 -0
  116. dclab/rtdc_dataset/fmt_hdf5/tables.py +60 -0
  117. dclab/rtdc_dataset/fmt_hierarchy/__init__.py +11 -0
  118. dclab/rtdc_dataset/fmt_hierarchy/base.py +278 -0
  119. dclab/rtdc_dataset/fmt_hierarchy/events.py +146 -0
  120. dclab/rtdc_dataset/fmt_hierarchy/hfilter.py +140 -0
  121. dclab/rtdc_dataset/fmt_hierarchy/mapper.py +134 -0
  122. dclab/rtdc_dataset/fmt_http.py +102 -0
  123. dclab/rtdc_dataset/fmt_s3.py +354 -0
  124. dclab/rtdc_dataset/fmt_tdms/__init__.py +476 -0
  125. dclab/rtdc_dataset/fmt_tdms/event_contour.py +264 -0
  126. dclab/rtdc_dataset/fmt_tdms/event_image.py +220 -0
  127. dclab/rtdc_dataset/fmt_tdms/event_mask.py +62 -0
  128. dclab/rtdc_dataset/fmt_tdms/event_trace.py +146 -0
  129. dclab/rtdc_dataset/fmt_tdms/exc.py +37 -0
  130. dclab/rtdc_dataset/fmt_tdms/naming.py +151 -0
  131. dclab/rtdc_dataset/load.py +77 -0
  132. dclab/rtdc_dataset/meta_table.py +25 -0
  133. dclab/rtdc_dataset/writer.py +1019 -0
  134. dclab/statistics.py +226 -0
  135. dclab/util.py +176 -0
  136. dclab/warn.py +15 -0
  137. dclab-0.67.0.dist-info/METADATA +153 -0
  138. dclab-0.67.0.dist-info/RECORD +142 -0
  139. dclab-0.67.0.dist-info/WHEEL +6 -0
  140. dclab-0.67.0.dist-info/entry_points.txt +8 -0
  141. dclab-0.67.0.dist-info/licenses/LICENSE +283 -0
  142. dclab-0.67.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,264 @@
1
+ """Class for efficiently handling contour data"""
2
+ import numbers
3
+ import sys
4
+ import warnings
5
+
6
+ import numpy as np
7
+
8
+ from ...features import inert_ratio
9
+
10
+ from .exc import ContourIndexingError
11
+
12
+
13
+ class ContourVerificationWarning(UserWarning):
14
+ pass
15
+
16
+
17
+ class ContourColumn(object):
18
+ def __init__(self, rtdc_dataset):
19
+ """A wrapper for ContourData that takes into account event offsets
20
+
21
+ Event offsets appear when the first event that is recorded in the
22
+ tdms files does not have a corresponding contour in the contour
23
+ text file.
24
+ """
25
+ fname = self.find_contour_file(rtdc_dataset)
26
+ if fname is None:
27
+ self.identifier = None
28
+ else:
29
+ if sys.version_info[0] == 2:
30
+ self.identifier = str(fname).decode("utf-8")
31
+ else:
32
+ self.identifier = str(fname)
33
+ if fname is not None:
34
+ self._contour_data = ContourData(fname)
35
+ self._initialized = False
36
+ else:
37
+ self._contour_data = []
38
+ # prevent `determine_offset` to be called
39
+ self._initialized = True
40
+ self.frame = rtdc_dataset["frame"]
41
+ if "image" in rtdc_dataset:
42
+ self.image_shape = rtdc_dataset["image"].shape[1:]
43
+ else:
44
+ self.image_shape = None
45
+ # if they are set, these features are used for verifying the contour
46
+ self.pxfeat = {}
47
+
48
+ if "area_msd" in rtdc_dataset:
49
+ self.pxfeat["area_msd"] = rtdc_dataset["area_msd"]
50
+ if "pixel size" in rtdc_dataset.config["imaging"]:
51
+ px_size = rtdc_dataset.config["imaging"]["pixel size"]
52
+ for key in ["pos_x", "pos_y", "size_x", "size_y"]:
53
+ if key not in rtdc_dataset.features_innate:
54
+ # abort
55
+ self.pxfeat.clear()
56
+ break
57
+ self.pxfeat[key] = rtdc_dataset[key] / px_size
58
+
59
+ self.event_offset = 0
60
+ self._length = None
61
+ self.dtype = np.int16
62
+
63
+ def __getitem__(self, idx):
64
+ if not isinstance(idx, numbers.Integral):
65
+ raise NotImplementedError(
66
+ "The RTDC_TDMS data handler does not support indexing with "
67
+ "anything else than scalar integers. Please convert your data "
68
+ "to the .rtdc file format!")
69
+
70
+ if not self._initialized:
71
+ self.determine_offset()
72
+ idnew = idx-self.event_offset
73
+ cdata = None
74
+ if idnew < 0:
75
+ # No contour data
76
+ cdata = np.zeros((2, 2), dtype=int)
77
+ else:
78
+ # Assign contour based on stored frame index
79
+ frame_ist = self.frame[idx]
80
+ # Do not only check the exact frame, but +/- 2 events around it
81
+ for idn in [idnew, idnew-1, idnew+1, idnew-2, idnew+2]:
82
+ # check frame
83
+ try:
84
+ frame_soll = self._contour_data.get_frame(idn)
85
+ except IndexError:
86
+ # reached end of file
87
+ continue
88
+ if np.allclose(frame_soll, frame_ist, rtol=0):
89
+ cdata = self._contour_data[idn]
90
+ break
91
+ if cdata is None and self.image_shape and self.pxfeat:
92
+ # The frame is wrong, but the contour might be correct.
93
+ # We check that by verifying several features.
94
+ cdata2 = self._contour_data[idnew]
95
+ cont = np.zeros((self.image_shape[1], self.image_shape[0]))
96
+ cont[cdata2[:, 0], cdata2[:, 1]] = True
97
+ mm = inert_ratio.cont_moments_cv(cdata2)
98
+
99
+ if (np.allclose(self.pxfeat["size_x"][idx],
100
+ np.ptp(cdata2[:, 0]) + 1,
101
+ rtol=0, atol=1e-5)
102
+ and np.allclose(self.pxfeat["size_y"][idx],
103
+ np.ptp(cdata2[:, 1]) + 1,
104
+ rtol=0, atol=1e-5)
105
+ and np.allclose(mm["m00"],
106
+ self.pxfeat["area_msd"][idx],
107
+ rtol=0, atol=1e-5)
108
+ # atol=6 for positions, because the original positions
109
+ # are computed from the convex contour, which would be
110
+ # computed using cv2.convexHull(cdata2).
111
+ and np.allclose(self.pxfeat["pos_x"][idx],
112
+ mm["m10"]/mm["m00"],
113
+ rtol=0, atol=6)
114
+ and np.allclose(self.pxfeat["pos_y"][idx],
115
+ mm["m01"]/mm["m00"],
116
+ rtol=0, atol=6)):
117
+ cdata = cdata2
118
+
119
+ if cdata is None:
120
+ # No idea what went wrong, but we make the beste guess and
121
+ # issue a warning.
122
+ cdata = self._contour_data[idnew]
123
+ frame_c = self._contour_data.get_frame(idnew)
124
+ warnings.warn(
125
+ "Couldn't verify contour {} in {}".format(idx, self.identifier)
126
+ + " (frame index {})!".format(frame_c),
127
+ ContourVerificationWarning
128
+ )
129
+ return cdata
130
+
131
+ def __len__(self):
132
+ if self._length is None:
133
+ length = len(self._contour_data)
134
+ if length:
135
+ if not self._initialized:
136
+ self.determine_offset()
137
+ length += self.event_offset
138
+ self._length = length
139
+ return self._length
140
+
141
+ @property
142
+ def shape(self):
143
+ return len(self), np.nan, 2
144
+
145
+ def determine_offset(self):
146
+ """Determines the offset of the contours w.r.t. other data columns
147
+
148
+ Notes
149
+ -----
150
+ - the "frame" column of `rtdc_dataset` is compared to
151
+ the first contour in the contour text file to determine an
152
+ offset by one event
153
+ - modifies the property `event_offset` and sets `_initialized`
154
+ to `True`
155
+ """
156
+ # In case of regular RTDC, the first contour is
157
+ # missing. In case of fRTDC, it is there, so we
158
+ # might have an offset. We find out if the first
159
+ # contour frame is missing by comparing it to
160
+ # the "frame" column of the rtdc dataset.
161
+ fref = self._contour_data.get_frame(0)
162
+ f0 = self.frame[0]
163
+ f1 = self.frame[1]
164
+ # Use allclose to avoid float/integer comparison problems
165
+ if np.allclose(fref, f0, rtol=0):
166
+ self.event_offset = 0
167
+ elif np.allclose(fref, f1, rtol=0):
168
+ self.event_offset = 1
169
+ else:
170
+ msg = "Contour data has unknown offset (frame {})!".format(fref)
171
+ raise ContourIndexingError(msg)
172
+ self._initialized = True
173
+
174
+ @staticmethod
175
+ def find_contour_file(rtdc_dataset):
176
+ """Tries to find a contour file that belongs to an RTDC dataset
177
+
178
+ Returns None if no contour file is found.
179
+ """
180
+ cont_id = rtdc_dataset.path.stem
181
+ cands = [c.name for c in rtdc_dataset._fdir.glob("*_contours.txt")]
182
+ cands = sorted(cands)
183
+ # Search for perfect matches, e.g.
184
+ # - M1_0.240000ul_s.tdms
185
+ # - M1_0.240000ul_s_contours.txt
186
+ for c1 in cands:
187
+ if c1.startswith(cont_id):
188
+ cfile = rtdc_dataset._fdir / c1
189
+ break
190
+ else:
191
+ # Search for M* matches with most overlap, e.g.
192
+ # - M1_0.240000ul_s.tdms
193
+ # - M1_contours.txt
194
+ for c2 in cands:
195
+ if (c2.split("_")[0] == rtdc_dataset._mid):
196
+ # Do not confuse with M10_contours.txt
197
+ cfile = rtdc_dataset._fdir / c2
198
+ break
199
+ else:
200
+ cfile = None
201
+ return cfile
202
+
203
+
204
+ class ContourData(object):
205
+ def __init__(self, fname):
206
+ """Access an MX_contour.txt as a dictionary
207
+
208
+ Initialize this class with a *_contour.txt file.
209
+ The individual contours can be accessed like a
210
+ list (enumerated from 0 on).
211
+ """
212
+ self._initialized = False
213
+ self.filename = fname
214
+ self._length = None
215
+
216
+ def __getitem__(self, idx):
217
+ cont = self.data[idx]
218
+ cont = cont.strip()
219
+ cont = cont.replace(")", "")
220
+ cont = cont.replace("(", "")
221
+ cont = cont.replace("(", "")
222
+ cont = cont.replace("\n", ",")
223
+ cont = cont.replace(" ", " ")
224
+ cont = cont.replace(" ", " ")
225
+ if len(cont) > 1:
226
+ _frame, cont = cont.split(" ", 1)
227
+ cont = cont.strip(" ,")
228
+ data = np.fromstring(cont, sep=",", dtype=np.uint16).reshape(-1, 2)
229
+ return data
230
+
231
+ def __len__(self):
232
+ if self._length is None:
233
+ self._length = len(self.data)
234
+ return self._length
235
+
236
+ def _index_file(self):
237
+ """Open and index the contour file
238
+
239
+ This function populates the internal list of contours
240
+ as strings which will be available as `self.data`.
241
+ """
242
+ with self.filename.open() as fd:
243
+ data = fd.read()
244
+
245
+ ident = "Contour in frame"
246
+ self._data = data.split(ident)[1:]
247
+ self._initialized = True
248
+
249
+ @property
250
+ def data(self):
251
+ """Access self.data
252
+ If `self._index_file` has not been computed before, this
253
+ property will cause it to do so.
254
+ """
255
+ if not self._initialized:
256
+ self._index_file()
257
+ return self._data
258
+
259
+ def get_frame(self, idx):
260
+ """Return the frame number of a contour"""
261
+ cont = self.data[idx]
262
+ # previously was split using " ", but "(" is more general
263
+ frame = int(cont.strip().split("(", 1)[0])
264
+ return frame
@@ -0,0 +1,220 @@
1
+ """
2
+ Class for efficiently handling image/video data
3
+ """
4
+ import numbers
5
+ import pathlib
6
+ import sys
7
+ import warnings
8
+
9
+ import imageio
10
+ import numpy as np
11
+
12
+ from .exc import (InvalidVideoFileError, CorruptFrameWarning,
13
+ InitialFrameMissingWarning, SlowVideoWarning)
14
+
15
+
16
+ ISWIN = sys.platform.startswith("win")
17
+
18
+
19
+ class ImageColumn(object):
20
+ def __init__(self, rtdc_dataset):
21
+ """A wrapper for ImageMap that takes into account event offsets
22
+
23
+ Event offsets appear when the first event that is recorded in the
24
+ tdms files does not have a corresponding cell image in the video
25
+ file.
26
+ """
27
+ fname = self.find_video_file(rtdc_dataset)
28
+ self.identifier = fname
29
+ if fname is not None:
30
+ self._image_data = ImageMap(fname)
31
+ else:
32
+ self._image_data = []
33
+ conf = rtdc_dataset.config
34
+ self.event_offset = int(conf["fmt_tdms"]["video frame offset"])
35
+ self.video_file = fname
36
+ self._shape = None
37
+ self.dtype = np.uint8
38
+
39
+ def __getitem__(self, idx):
40
+ if not isinstance(idx, numbers.Integral):
41
+ raise NotImplementedError(
42
+ "The RTDC_TDMS data handler does not support indexing with "
43
+ "anything else than scalar integers. Please convert your data "
44
+ "to the .rtdc file format!")
45
+
46
+ idnew = int(idx-self.event_offset)
47
+ if idnew < 0:
48
+ # No data - show a dummy image instead
49
+ warnings.warn("Frame {} in {} ".format(idnew, self.identifier)
50
+ + "is not defined; replacing with dummy image!",
51
+ InitialFrameMissingWarning)
52
+ cdata = self.dummy
53
+ else:
54
+ if hasattr(imageio.plugins.ffmpeg, "CannotReadFrameError"):
55
+ # imageio<2.5.0
56
+ excs = IndexError, imageio.plugins.ffmpeg.CannotReadFrameError
57
+ else:
58
+ # imageio>=2.5.0
59
+ excs = IndexError
60
+ try:
61
+ cdata = self._image_data[idnew]
62
+ except excs:
63
+ # The avi is corrupt. Return a dummy image.
64
+ warnings.warn("Frame {} in {} ".format(idnew, self.identifier)
65
+ + "is corrupt; replacing with dummy image!",
66
+ CorruptFrameWarning)
67
+ cdata = self.dummy
68
+ return cdata
69
+
70
+ def __len__(self):
71
+ length = len(self._image_data)
72
+ if length:
73
+ length = length + self.event_offset
74
+ return length
75
+
76
+ @property
77
+ def dummy(self):
78
+ """Returns a dummy image"""
79
+ cdata = np.zeros(self.shape[1:], dtype=np.uint8)
80
+ return cdata
81
+
82
+ @property
83
+ def shape(self):
84
+ if self._shape is None:
85
+ f0 = self._image_data[0].shape
86
+ self._shape = len(self), f0[0], f0[1]
87
+ return self._shape
88
+
89
+ @staticmethod
90
+ def find_video_file(rtdc_dataset):
91
+ """Tries to find a video file that belongs to an RTDC dataset
92
+
93
+ Returns None if no video file is found.
94
+ """
95
+ video = None
96
+ if rtdc_dataset._fdir.exists():
97
+ # Cell images (video)
98
+ videos = [v.name for v in rtdc_dataset._fdir.glob("*.avi")]
99
+ # Filter videos according to measurement number
100
+ meas_id = rtdc_dataset._mid
101
+ videos = [v for v in videos if v.split("_")[0] == meas_id]
102
+ videos.sort()
103
+ if len(videos) != 0:
104
+ # Defaults to first avi file
105
+ video = videos[0]
106
+ # g/q video file names. q comes first.
107
+ for v in videos:
108
+ if v.endswith("imag.avi"):
109
+ video = v
110
+ break
111
+ # add this here, because fRT-DC measurements also contain
112
+ # videos ..._proc.avi
113
+ elif v.endswith("imaq.avi"):
114
+ video = v
115
+ break
116
+ if video is None:
117
+ return None
118
+ else:
119
+ vpath = rtdc_dataset._fdir / video
120
+ if vpath.stat().st_size < 64:
121
+ # the video file is empty
122
+ raise InvalidVideoFileError("Bad video '{}'!".format(vpath))
123
+ else:
124
+ return vpath
125
+
126
+
127
+ class ImageMap(object):
128
+ def __init__(self, fname):
129
+ """Access a video file of an RT-DC dataset
130
+
131
+ Initialize this class with a video file.
132
+ """
133
+ fname = pathlib.Path(fname)
134
+ self._length = None
135
+ # video handle:
136
+ self._cap = None
137
+ # filename
138
+ if not fname.exists():
139
+ raise OSError("file does not exist: {}".format(fname))
140
+ self.filename = fname
141
+
142
+ def __del__(self):
143
+ if self._cap is not None and hasattr(self._cap, "_proc"):
144
+ if ISWIN and self._cap._proc is not None:
145
+ # This is a workaround for windows when pytest fails due
146
+ # to "OSError: [WinError 6] The handle is invalid",
147
+ # which is somehow related to the fact that "_proc.kill()"
148
+ # must be called twice (in "close()" and in this case) in
149
+ # order to terminate the process and due to the fact the
150
+ # we are not using the with-statement in combination
151
+ # with imageio.get_reader().
152
+ self._cap._proc.kill()
153
+ self._cap.close()
154
+
155
+ def __getitem__(self, idx):
156
+ """Returns the requested frame from the video in gray scale"""
157
+ cap = self.video_handle
158
+ try:
159
+ cellimg = cap.get_data(idx)
160
+ except IndexError:
161
+ self._get_image_workaround_seek(idx)
162
+ else:
163
+ if np.all(cellimg == 0):
164
+ cellimg = self._get_image_workaround_seek(idx)
165
+ # Convert to grayscale
166
+ if len(cellimg.shape) == 3:
167
+ cellimg = np.array(cellimg[:, :, 0])
168
+ return cellimg
169
+
170
+ def __len__(self):
171
+ """Returns the length of the video or `True` if the length cannot be
172
+ determined.
173
+ """
174
+ if self._length is None:
175
+ cap = self.video_handle
176
+ if hasattr(cap, "count_frames"): # imageio>=2.5.0
177
+ length = cap.count_frames()
178
+ else: # imageio<2.5.0
179
+ try:
180
+ length = len(cap)
181
+ except TypeError:
182
+ # length is set to inf (it would generally still be
183
+ # possible to rescue a limted number of frames, but
184
+ # other parts of the dataset are probably also broken
185
+ # (aborted measurement), so for the sake of simplicity
186
+ # we stop here)
187
+ raise InvalidVideoFileError(
188
+ "Video file has unknown length '{}'!".format(
189
+ self.filename))
190
+ self._length = length
191
+ return self._length
192
+
193
+ def _get_image_workaround_seek(self, idx):
194
+ """Same as __getitem__ but seek through the video beforehand
195
+
196
+ This is a workaround for an all-zero image returned by `imageio`.
197
+ """
198
+ cap = self.video_handle
199
+ mult = 50
200
+ # get the first frame to be on the safe side
201
+ cap.get_data(101) # frame above 100 (don't know why)
202
+ cap.get_data(0) # seek to zero
203
+ for ii in range(idx//mult):
204
+ cap.get_data(ii*mult)
205
+ final = cap.get_data(idx)
206
+ if not np.all(final == 0):
207
+ # This means we succeeded
208
+ warnings.warn("Seeking video file does not work, used workaround "
209
+ + "which is slow!", SlowVideoWarning)
210
+ return final
211
+
212
+ @property
213
+ def video_handle(self):
214
+ if self._cap is None:
215
+ try:
216
+ self._cap = imageio.get_reader(self.filename)
217
+ except OSError:
218
+ raise InvalidVideoFileError(
219
+ "Broken video file '{}'!".format(self.filename))
220
+ return self._cap
@@ -0,0 +1,62 @@
1
+ """Class for on-the-fly conversion of contours to masks"""
2
+ import numbers
3
+
4
+ import numpy as np
5
+ import scipy.ndimage as ndi
6
+
7
+
8
+ class MaskColumn(object):
9
+ def __init__(self, rtdc_dataset):
10
+ """Computes mask from contour data"""
11
+ self.contour = rtdc_dataset["contour"]
12
+ self.image = rtdc_dataset["image"]
13
+ self.identifier = self.contour.identifier
14
+ self.config = rtdc_dataset.config
15
+ self._shape = None
16
+ self._img_shape_cache = None
17
+ self.dtype = np.bool_
18
+
19
+ def __getitem__(self, idx):
20
+ if not isinstance(idx, numbers.Integral):
21
+ raise NotImplementedError(
22
+ "The RTDC_TDMS data handler does not support indexing with "
23
+ "anything else than scalar integers. Please convert your data "
24
+ "to the .rtdc file format!")
25
+
26
+ mask = np.zeros(self._img_shape, dtype=bool)
27
+ conti = self.contour[idx]
28
+ mask[conti[:, 1], conti[:, 0]] = True
29
+ ndi.binary_fill_holes(mask, output=mask)
30
+ return mask
31
+
32
+ def __len__(self):
33
+ if self._img_shape != (0, 0):
34
+ lc = len(self.contour)
35
+ else:
36
+ lc = 0
37
+ return lc
38
+
39
+ @property
40
+ def _img_shape(self):
41
+ if self._img_shape_cache is None:
42
+ """Shape of one event image"""
43
+ cfgim = self.config["imaging"]
44
+ if self.image:
45
+ # get shape from image column
46
+ event_image_shape = self.image.shape[1:]
47
+ elif "roi size x" in cfgim and "roi size y" in cfgim:
48
+ # get shape from config (this is less reliable than getting
49
+ # the shape from the image; there were measurements with
50
+ # wrong config keys)
51
+ event_image_shape = (cfgim["roi size y"], cfgim["roi size x"])
52
+ else:
53
+ # no shape available
54
+ event_image_shape = (0, 0)
55
+ self._img_shape_cache = event_image_shape
56
+ return self._img_shape_cache
57
+
58
+ @property
59
+ def shape(self):
60
+ if self._shape is None:
61
+ self._shape = len(self), self._img_shape[0], self._img_shape[1]
62
+ return self._shape
@@ -0,0 +1,146 @@
1
+ """Handling fluorescence trace data"""
2
+ import pathlib
3
+ import warnings
4
+
5
+ from nptdms import TdmsFile
6
+ import numpy as np
7
+
8
+ from ... import definitions as dfn
9
+
10
+ from . import naming
11
+ from .exc import InvalidTDMSFileFormatError, MultipleSamplesPerEventFound
12
+
13
+
14
+ class TraceColumn(object):
15
+ def __init__(self, rtdc_dataset):
16
+ """Prepares everything but does not load the trace data yet
17
+
18
+ The trace data is loaded when __getitem__, __len__, or __iter__
19
+ are called. This saves time and memory when the trace data is
20
+ not needed at all, e.g. for batch processing with DCscope.
21
+ """
22
+ self._trace = None
23
+ self.mname = rtdc_dataset.path
24
+ self.identifier = self.mname
25
+ self.dtype = np.int16
26
+
27
+ def __getitem__(self, trace_key):
28
+ if trace_key not in dfn.FLUOR_TRACES:
29
+ msg = "Unknown fluorescence trace key: {}".format(trace_key)
30
+ raise ValueError(msg)
31
+ return self.trace.__getitem__(trace_key)
32
+
33
+ def __len__(self):
34
+ return self.trace.__len__()
35
+
36
+ def __iter__(self):
37
+ return self.trace.__iter__()
38
+
39
+ def __repr__(self):
40
+ tname = TraceColumn.find_trace_file(self.mname)
41
+ if self._trace is None:
42
+ addstr = "not loaded into memory"
43
+ else:
44
+ addstr = "loaded into memory"
45
+
46
+ if tname is None:
47
+ rep = "No trace data available!"
48
+ else:
49
+ rep = "Fluorescence trace data from file {}, <{}>".format(tname,
50
+ addstr)
51
+ return rep
52
+
53
+ def keys(self):
54
+ return self.trace.keys()
55
+
56
+ @property
57
+ def shape(self):
58
+ key0 = sorted(self.keys())[0]
59
+ return len(self), len(self[key0]), len(self[key0][0])
60
+
61
+ @property
62
+ def trace(self):
63
+ """Initializes the trace data"""
64
+ if self._trace is None:
65
+ self._trace = self.load_trace(self.mname)
66
+ return self._trace
67
+
68
+ @staticmethod
69
+ def load_trace(mname):
70
+ """Loads the traces and returns them as a dictionary
71
+
72
+ Currently, only loading traces from tdms files is supported.
73
+ This forces us to load the full tdms file into memory which
74
+ takes some time.
75
+ """
76
+ tname = TraceColumn.find_trace_file(mname)
77
+
78
+ # Initialize empty trace dictionary
79
+ trace = {}
80
+
81
+ if tname is None:
82
+ pass
83
+ elif tname.suffix == ".tdms":
84
+ # Again load the measurement tdms file.
85
+ # This might increase memory usage, but it is cleaner
86
+ # when looking at code structure.
87
+ mdata = TdmsFile(str(mname))
88
+ try:
89
+ sampleids = mdata["Cell Track"]["FL1index"].data
90
+ except KeyError:
91
+ raise InvalidTDMSFileFormatError(
92
+ "No 'FL1index' column in '{}'!".format(tname))
93
+
94
+ # Check that sample IDs are always incremented with same
95
+ # sample size.
96
+ samples_per_event = np.unique(np.diff(sampleids))
97
+ if len(samples_per_event) > 1:
98
+ # This means the length of the fluorescence trace is not
99
+ # a constant. According to Philipp, this means the trace
100
+ # cannot be used.
101
+ warnings.warn("Ignoring trace data of '{}' ".format(tname)
102
+ + "due to multiple values for samples per "
103
+ + "event: {}".format(samples_per_event),
104
+ MultipleSamplesPerEventFound)
105
+ else:
106
+ # Load the trace data. The traces file is usually larger than
107
+ # the measurement file.
108
+ tdata = TdmsFile(str(tname))
109
+ for trace_key in dfn.FLUOR_TRACES:
110
+ group, ch = naming.tr_data_map[trace_key]
111
+ try:
112
+ trdat = tdata[group][ch].data
113
+ except KeyError:
114
+ pass
115
+ else:
116
+ if trdat is not None and trdat.size != 0:
117
+ # Split the input trace data into equally-spaced
118
+ # sections (we already tested that sampleids is
119
+ # equally-spaced).
120
+ spe = sampleids[1] - sampleids[0]
121
+ if (trdat.size % spe) == 0:
122
+ # this is the ideal case
123
+ trace_array = trdat.reshape(
124
+ trdat.size // spe, -1)
125
+ else:
126
+ # this is bad, but we allow it
127
+ trace_array = np.split(trdat, trdat.size//spe)
128
+ trace[trace_key] = trace_array
129
+
130
+ return trace
131
+
132
+ @staticmethod
133
+ def find_trace_file(mname):
134
+ """Tries to find the traces tdms file name
135
+
136
+ Returns None if no trace file is found.
137
+ """
138
+ mname = pathlib.Path(mname)
139
+ tname = None
140
+
141
+ if mname.exists():
142
+ cand = mname.with_name(mname.name[:-5] + "_traces.tdms")
143
+ if cand.exists():
144
+ tname = cand
145
+
146
+ return tname