phasorpy 0.4__cp313-cp313-win_amd64.whl → 0.6__cp313-cp313-win_amd64.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.
phasorpy/__init__.py CHANGED
@@ -2,9 +2,8 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- __all__ = ['__version__', 'versions']
5
+ __all__ = ['__version__']
6
6
 
7
- from .version import __version__, versions
8
7
 
9
- __version__ = __version__ # pylint: disable=self-assigning-variable
8
+ __version__ = '0.6'
10
9
  """PhasorPy version string."""
Binary file
phasorpy/_phasorpy.pyx CHANGED
@@ -4,15 +4,10 @@
4
4
  # cython: wraparound = False
5
5
  # cython: cdivision = True
6
6
  # cython: nonecheck = False
7
+ # cython: freethreading_compatible = True
7
8
 
8
9
  """Cython implementation of low-level functions for the PhasorPy library."""
9
10
 
10
- # TODO: replace short with unsigned char when Cython supports it
11
- # https://github.com/cython/cython/pull/6196#issuecomment-2209509572
12
-
13
- # TODO: use fused return types for functions returning more than two items
14
- # https://github.com/cython/cython/issues/6328
15
-
16
11
  cimport cython
17
12
 
18
13
  from cython.parallel import parallel, prange
@@ -55,6 +50,12 @@ ctypedef fused uint_t:
55
50
  uint32_t
56
51
  uint64_t
57
52
 
53
+ ctypedef fused int_t:
54
+ int8_t
55
+ int16_t
56
+ int32_t
57
+ int64_t
58
+
58
59
  ctypedef fused signal_t:
59
60
  uint8_t
60
61
  uint16_t
@@ -451,7 +452,7 @@ cdef (double, double) _phasor_from_fret_donor(
451
452
  double omega,
452
453
  double donor_lifetime,
453
454
  double fret_efficiency,
454
- double donor_freting,
455
+ double donor_fretting,
455
456
  double donor_background,
456
457
  double background_real,
457
458
  double background_imag,
@@ -471,16 +472,16 @@ cdef (double, double) _phasor_from_fret_donor(
471
472
  elif fret_efficiency > 1.0:
472
473
  fret_efficiency = 1.0
473
474
 
474
- if donor_freting < 0.0:
475
- donor_freting = 0.0
476
- elif donor_freting > 1.0:
477
- donor_freting = 1.0
475
+ if donor_fretting < 0.0:
476
+ donor_fretting = 0.0
477
+ elif donor_fretting > 1.0:
478
+ donor_fretting = 1.0
478
479
 
479
480
  if donor_background < 0.0:
480
481
  donor_background = 0.0
481
482
 
482
- f_pure = 1.0 - donor_freting
483
- f_quenched = (1.0 - fret_efficiency) * donor_freting
483
+ f_pure = 1.0 - donor_fretting
484
+ f_quenched = (1.0 - fret_efficiency) * donor_fretting
484
485
  sum = f_pure + f_quenched + donor_background
485
486
  if sum < 1e-9:
486
487
  # no signal in donor channel
@@ -516,7 +517,7 @@ cdef (double, double) _phasor_from_fret_acceptor(
516
517
  double donor_lifetime,
517
518
  double acceptor_lifetime,
518
519
  double fret_efficiency,
519
- double donor_freting,
520
+ double donor_fretting,
520
521
  double donor_bleedthrough,
521
522
  double acceptor_bleedthrough,
522
523
  double acceptor_background,
@@ -541,10 +542,10 @@ cdef (double, double) _phasor_from_fret_acceptor(
541
542
  elif fret_efficiency > 1.0:
542
543
  fret_efficiency = 1.0
543
544
 
544
- if donor_freting < 0.0:
545
- donor_freting = 0.0
546
- elif donor_freting > 1.0:
547
- donor_freting = 1.0
545
+ if donor_fretting < 0.0:
546
+ donor_fretting = 0.0
547
+ elif donor_fretting > 1.0:
548
+ donor_fretting = 1.0
548
549
 
549
550
  if donor_bleedthrough < 0.0:
550
551
  donor_bleedthrough = 0.0
@@ -575,7 +576,7 @@ cdef (double, double) _phasor_from_fret_acceptor(
575
576
  quenched_imag,
576
577
  1.0,
577
578
  1.0 - fret_efficiency,
578
- 1.0 - donor_freting
579
+ 1.0 - donor_fretting
579
580
  )
580
581
 
581
582
  # phasor of acceptor at frequency
@@ -597,8 +598,8 @@ cdef (double, double) _phasor_from_fret_acceptor(
597
598
  sensitized_imag = mod * sin(phi)
598
599
 
599
600
  # weighted average
600
- f_donor = donor_bleedthrough * (1.0 - donor_freting * fret_efficiency)
601
- f_acceptor = donor_freting * fret_efficiency
601
+ f_donor = donor_bleedthrough * (1.0 - donor_fretting * fret_efficiency)
602
+ f_acceptor = donor_fretting * fret_efficiency
602
603
  sum = f_donor + f_acceptor + acceptor_bleedthrough + acceptor_background
603
604
  if sum < 1e-9:
604
605
  # no signal in acceptor channel
@@ -772,6 +773,33 @@ cdef (float_t, float_t) _phasor_from_apparent_lifetime(
772
773
  return <float_t> (mod * cos(phi)), <float_t> (mod * sin(phi))
773
774
 
774
775
 
776
+ @cython.ufunc
777
+ cdef float_t _phasor_to_normal_lifetime(
778
+ float_t real,
779
+ float_t imag,
780
+ float_t omega,
781
+ ) noexcept nogil:
782
+ """Return normal lifetimes from phasor coordinates."""
783
+ cdef:
784
+ double taunorm = INFINITY
785
+ double t
786
+
787
+ if isnan(real) or isnan(imag):
788
+ return <float_t> NAN
789
+
790
+ omega *= omega
791
+ if omega > 0.0:
792
+ t = 0.5 * (1.0 + cos(atan2(imag, real - 0.5)))
793
+ if t <= 0.0:
794
+ taunorm = INFINITY
795
+ elif t > 1.0:
796
+ taunorm = NAN
797
+ else:
798
+ taunorm = sqrt((1.0 - t) / (omega * t))
799
+
800
+ return <float_t> taunorm
801
+
802
+
775
803
  @cython.ufunc
776
804
  cdef (float_t, float_t) _phasor_from_single_lifetime(
777
805
  float_t lifetime,
@@ -919,7 +947,7 @@ cdef (float_t, float_t) _phasor_at_harmonic(
919
947
  int harmonic,
920
948
  int other_harmonic,
921
949
  ) noexcept nogil:
922
- """Return phasor coordinates on semicircle at other harmonic."""
950
+ """Return phasor coordinates on universal semicircle at other harmonic."""
923
951
  if isnan(real):
924
952
  return <float_t> NAN, <float_t> NAN
925
953
 
@@ -982,7 +1010,7 @@ cdef (float_t, float_t) _phasor_divide(
982
1010
 
983
1011
 
984
1012
  @cython.ufunc
985
- cdef short _is_inside_range(
1013
+ cdef unsigned char _is_inside_range(
986
1014
  float_t x, # point
987
1015
  float_t y,
988
1016
  float_t xmin, # x range
@@ -1002,7 +1030,7 @@ cdef short _is_inside_range(
1002
1030
 
1003
1031
 
1004
1032
  @cython.ufunc
1005
- cdef short _is_inside_rectangle(
1033
+ cdef unsigned char _is_inside_rectangle(
1006
1034
  float_t x, # point
1007
1035
  float_t y,
1008
1036
  float_t x0, # segment start
@@ -1044,7 +1072,7 @@ cdef short _is_inside_rectangle(
1044
1072
 
1045
1073
 
1046
1074
  @cython.ufunc
1047
- cdef short _is_inside_polar_rectangle(
1075
+ cdef unsigned char _is_inside_polar_rectangle(
1048
1076
  float_t x, # point
1049
1077
  float_t y,
1050
1078
  float_t angle_min, # phase, -pi to pi
@@ -1054,7 +1082,7 @@ cdef short _is_inside_polar_rectangle(
1054
1082
  ) noexcept nogil:
1055
1083
  """Return whether point is inside polar rectangle.
1056
1084
 
1057
- Angles should be in range -pi to pi, else performance is degraded.
1085
+ Angles should be in range [-pi, pi], else performance is degraded.
1058
1086
 
1059
1087
  """
1060
1088
  cdef:
@@ -1083,7 +1111,7 @@ cdef short _is_inside_polar_rectangle(
1083
1111
 
1084
1112
 
1085
1113
  @cython.ufunc
1086
- cdef short _is_inside_circle(
1114
+ cdef unsigned char _is_inside_circle(
1087
1115
  float_t x, # point
1088
1116
  float_t y,
1089
1117
  float_t x0, # circle center
@@ -1100,7 +1128,7 @@ cdef short _is_inside_circle(
1100
1128
 
1101
1129
 
1102
1130
  @cython.ufunc
1103
- cdef short _is_inside_ellipse(
1131
+ cdef unsigned char _is_inside_ellipse(
1104
1132
  float_t x, # point
1105
1133
  float_t y,
1106
1134
  float_t x0, # ellipse center
@@ -1135,7 +1163,7 @@ cdef short _is_inside_ellipse(
1135
1163
 
1136
1164
 
1137
1165
  @cython.ufunc
1138
- cdef short _is_inside_ellipse_(
1166
+ cdef unsigned char _is_inside_ellipse_(
1139
1167
  float_t x, # point
1140
1168
  float_t y,
1141
1169
  float_t x0, # ellipse center
@@ -1164,7 +1192,7 @@ cdef short _is_inside_ellipse_(
1164
1192
 
1165
1193
 
1166
1194
  @cython.ufunc
1167
- cdef short _is_inside_stadium(
1195
+ cdef unsigned char _is_inside_stadium(
1168
1196
  float_t x, # point
1169
1197
  float_t y,
1170
1198
  float_t x0, # line start
@@ -1210,7 +1238,45 @@ _is_near_segment = _is_inside_stadium
1210
1238
 
1211
1239
 
1212
1240
  @cython.ufunc
1213
- cdef short _is_near_line(
1241
+ cdef unsigned char _is_inside_semicircle(
1242
+ float_t x, # point
1243
+ float_t y,
1244
+ float_t r, # distance
1245
+ ) noexcept nogil:
1246
+ """Return whether point is inside universal semicircle."""
1247
+ if r < 0.0 or isnan(x) or isnan(y):
1248
+ return False
1249
+ if y < -r:
1250
+ return False
1251
+ if y <= 0.0:
1252
+ if x >= 0.0 and x <= 1.0:
1253
+ return True
1254
+ # near endpoints?
1255
+ if x > 0.5:
1256
+ x -= <float_t> 1.0
1257
+ return x * x + y * y <= r * r
1258
+ return hypot(x - 0.5, y) <= r + 0.5
1259
+
1260
+
1261
+ @cython.ufunc
1262
+ cdef unsigned char _is_near_semicircle(
1263
+ float_t x, # point
1264
+ float_t y,
1265
+ float_t r, # distance
1266
+ ) noexcept nogil:
1267
+ """Return whether point is near universal semicircle."""
1268
+ if r < 0.0 or isnan(x) or isnan(y):
1269
+ return False
1270
+ if y < 0.0:
1271
+ # near endpoints?
1272
+ if x > 0.5:
1273
+ x -= <float_t> 1.0
1274
+ return x * x + y * y <= r * r
1275
+ return fabs(hypot(x - 0.5, y) - 0.5) <= r
1276
+
1277
+
1278
+ @cython.ufunc
1279
+ cdef unsigned char _is_near_line(
1214
1280
  float_t x, # point
1215
1281
  float_t y,
1216
1282
  float_t x0, # line start
@@ -1476,7 +1542,23 @@ cdef float_t _distance_from_line(
1476
1542
 
1477
1543
 
1478
1544
  @cython.ufunc
1479
- cdef (double, double, double) _segment_direction_and_length(
1545
+ cdef float_t _distance_from_semicircle(
1546
+ float_t x, # point
1547
+ float_t y,
1548
+ ) noexcept nogil:
1549
+ """Return distance from universal semicircle."""
1550
+ if isnan(x) or isnan(y):
1551
+ return NAN
1552
+ if y < 0.0:
1553
+ # distance to endpoints
1554
+ if x > 0.5:
1555
+ x -= <float_t> 1.0
1556
+ return <float_t> hypot(x, y)
1557
+ return <float_t> fabs(hypot(x - 0.5, y) - 0.5)
1558
+
1559
+
1560
+ @cython.ufunc
1561
+ cdef (float_t, float_t, float_t) _segment_direction_and_length(
1480
1562
  float_t x0, # segment start
1481
1563
  float_t y0,
1482
1564
  float_t x1, # segment end
@@ -1500,7 +1582,7 @@ cdef (double, double, double) _segment_direction_and_length(
1500
1582
 
1501
1583
 
1502
1584
  @cython.ufunc
1503
- cdef (double, double, double, double) _intersection_circle_circle(
1585
+ cdef (float_t, float_t, float_t, float_t) _intersect_circle_circle(
1504
1586
  float_t x0, # circle 0
1505
1587
  float_t y0,
1506
1588
  float_t r0,
@@ -1538,15 +1620,15 @@ cdef (double, double, double, double) _intersection_circle_circle(
1538
1620
  hd = sqrt(dd) / dr
1539
1621
  ld = ll / dr
1540
1622
  return (
1541
- ld * dx + hd * dy + x0,
1542
- ld * dy - hd * dx + y0,
1543
- ld * dx - hd * dy + x0,
1544
- ld * dy + hd * dx + y0,
1623
+ <float_t> (ld * dx + hd * dy + x0),
1624
+ <float_t> (ld * dy - hd * dx + y0),
1625
+ <float_t> (ld * dx - hd * dy + x0),
1626
+ <float_t> (ld * dy + hd * dx + y0),
1545
1627
  )
1546
1628
 
1547
1629
 
1548
1630
  @cython.ufunc
1549
- cdef (double, double, double, double) _intersection_circle_line(
1631
+ cdef (float_t, float_t, float_t, float_t) _intersect_circle_line(
1550
1632
  float_t x, # circle
1551
1633
  float_t y,
1552
1634
  float_t r,
@@ -1581,17 +1663,113 @@ cdef (double, double, double, double) _intersection_circle_line(
1581
1663
  return NAN, NAN, NAN, NAN
1582
1664
  rdd = sqrt(rdd)
1583
1665
  return (
1584
- x + (dd * dy + copysign(1.0, dy) * dx * rdd) / dr,
1585
- y + (-dd * dx + fabs(dy) * rdd) / dr,
1586
- x + (dd * dy - copysign(1.0, dy) * dx * rdd) / dr,
1587
- y + (-dd * dx - fabs(dy) * rdd) / dr,
1666
+ x + <float_t> ((dd * dy + copysign(1.0, dy) * dx * rdd) / dr),
1667
+ y + <float_t> ((-dd * dx + fabs(dy) * rdd) / dr),
1668
+ x + <float_t> ((dd * dy - copysign(1.0, dy) * dx * rdd) / dr),
1669
+ y + <float_t> ((-dd * dx - fabs(dy) * rdd) / dr),
1588
1670
  )
1589
1671
 
1590
1672
 
1673
+ @cython.ufunc
1674
+ cdef (float_t, float_t, float_t, float_t) _intersect_semicircle_line(
1675
+ float_t x0, # line start
1676
+ float_t y0,
1677
+ float_t x1, # line end
1678
+ float_t y1,
1679
+ ) noexcept nogil:
1680
+ """Return coordinates of intersections of line and universal semicircle."""
1681
+ cdef:
1682
+ double dx, dy, dr, dd, rdd
1683
+
1684
+ if isnan(x0) or isnan(x1) or isnan(y0) or isnan(y1):
1685
+ return NAN, NAN, NAN, NAN
1686
+
1687
+ dx = x1 - x0
1688
+ dy = y1 - y0
1689
+ dr = dx * dx + dy * dy
1690
+ dd = (x0 - 0.5) * y1 - (x1 - 0.5) * y0
1691
+ rdd = 0.25 * dr - dd * dd # discriminant
1692
+ if rdd < 0.0 or dr <= 0.0:
1693
+ # no intersection
1694
+ return NAN, NAN, NAN, NAN
1695
+ rdd = sqrt(rdd)
1696
+ x0 = <float_t> ((dd * dy - copysign(1.0, dy) * dx * rdd) / dr + 0.5)
1697
+ y0 = <float_t> ((-dd * dx - fabs(dy) * rdd) / dr)
1698
+ x1 = <float_t> ((dd * dy + copysign(1.0, dy) * dx * rdd) / dr + 0.5)
1699
+ y1 = <float_t> ((-dd * dx + fabs(dy) * rdd) / dr)
1700
+ if y0 < 0.0:
1701
+ x0 = NAN
1702
+ y0 = NAN
1703
+ if y1 < 0.0:
1704
+ x1 = NAN
1705
+ y1 = NAN
1706
+ return x0, y0, x1, y1
1707
+
1708
+
1709
+ def _nearest_neighbor_2d(
1710
+ int_t[::1] indices,
1711
+ const float_t[::1] x0,
1712
+ const float_t[::1] y0,
1713
+ const float_t[::1] x1,
1714
+ const float_t[::1] y1,
1715
+ const float_t distance_max,
1716
+ const int num_threads
1717
+ ):
1718
+ """Find nearest neighbors in 2D.
1719
+
1720
+ For each point in the first set of arrays (x0, y0) find the nearest point
1721
+ in the second set of arrays (x1, y1) and store the index of the nearest
1722
+ point in the second array in the indices array.
1723
+ If any coordinates are NaN, or the distance to the nearest point
1724
+ is larger than distance_max, the index is set to -1.
1725
+
1726
+ """
1727
+ cdef:
1728
+ ssize_t i, j, index
1729
+ float_t x, y, dmin
1730
+ float_t distance_max_squared = distance_max * distance_max
1731
+
1732
+ if (
1733
+ indices.shape[0] != x0.shape[0]
1734
+ or x0.shape[0] != y0.shape[0]
1735
+ or x1.shape[0] != y1.shape[0]
1736
+ ):
1737
+ raise ValueError('input array size mismatch')
1738
+
1739
+ with nogil, parallel(num_threads=num_threads):
1740
+ for i in prange(x0.shape[0]):
1741
+ x = x0[i]
1742
+ y = y0[i]
1743
+ if isnan(x) or isnan(y):
1744
+ indices[i] = -1
1745
+ continue
1746
+ index = -1
1747
+ dmin = INFINITY
1748
+ for j in range(x1.shape[0]):
1749
+ x = x0[i] - x1[j]
1750
+ y = y0[i] - y1[j]
1751
+ x = x * x + y * y
1752
+ if x < dmin:
1753
+ dmin = x
1754
+ index = j
1755
+ indices[i] = -1 if dmin > distance_max_squared else <int_t> index
1756
+
1757
+
1591
1758
  ###############################################################################
1592
1759
  # Blend ufuncs
1593
1760
 
1594
1761
 
1762
+ @cython.ufunc
1763
+ cdef float_t _blend_and(
1764
+ float_t a, # base layer
1765
+ float_t b, # blend layer
1766
+ ) noexcept nogil:
1767
+ """Return blended layers using `and` mode."""
1768
+ if isnan(a):
1769
+ return NAN
1770
+ return b
1771
+
1772
+
1595
1773
  @cython.ufunc
1596
1774
  cdef float_t _blend_normal(
1597
1775
  float_t a, # base layer
@@ -1665,7 +1843,7 @@ cdef float_t _blend_lighten(
1665
1843
 
1666
1844
 
1667
1845
  @cython.ufunc
1668
- cdef (double, double, double) _phasor_threshold_open(
1846
+ cdef (float_t, float_t, float_t) _phasor_threshold_open(
1669
1847
  float_t mean,
1670
1848
  float_t real,
1671
1849
  float_t imag,
@@ -1727,7 +1905,7 @@ cdef (double, double, double) _phasor_threshold_open(
1727
1905
 
1728
1906
 
1729
1907
  @cython.ufunc
1730
- cdef (double, double, double) _phasor_threshold_closed(
1908
+ cdef (float_t, float_t, float_t) _phasor_threshold_closed(
1731
1909
  float_t mean,
1732
1910
  float_t real,
1733
1911
  float_t imag,
@@ -1789,7 +1967,7 @@ cdef (double, double, double) _phasor_threshold_closed(
1789
1967
 
1790
1968
 
1791
1969
  @cython.ufunc
1792
- cdef (double, double, double) _phasor_threshold_mean_open(
1970
+ cdef (float_t, float_t, float_t) _phasor_threshold_mean_open(
1793
1971
  float_t mean,
1794
1972
  float_t real,
1795
1973
  float_t imag,
@@ -1809,7 +1987,7 @@ cdef (double, double, double) _phasor_threshold_mean_open(
1809
1987
 
1810
1988
 
1811
1989
  @cython.ufunc
1812
- cdef (double, double, double) _phasor_threshold_mean_closed(
1990
+ cdef (float_t, float_t, float_t) _phasor_threshold_mean_closed(
1813
1991
  float_t mean,
1814
1992
  float_t real,
1815
1993
  float_t imag,
@@ -1829,7 +2007,7 @@ cdef (double, double, double) _phasor_threshold_mean_closed(
1829
2007
 
1830
2008
 
1831
2009
  @cython.ufunc
1832
- cdef (double, double, double) _phasor_threshold_nan(
2010
+ cdef (float_t, float_t, float_t) _phasor_threshold_nan(
1833
2011
  float_t mean,
1834
2012
  float_t real,
1835
2013
  float_t imag,
@@ -2171,6 +2349,7 @@ def _median_filter_2d(
2171
2349
  # Decoder functions
2172
2350
 
2173
2351
 
2352
+ @cython.boundscheck(True)
2174
2353
  def _flimlabs_signal(
2175
2354
  uint_t[:, :, ::] signal, # channel, pixel, bin
2176
2355
  list data, # list[list[list[[int, int]]]]
@@ -2178,6 +2357,7 @@ def _flimlabs_signal(
2178
2357
  ):
2179
2358
  """Return TCSPC histogram image from FLIM LABS JSON intensity data."""
2180
2359
  cdef:
2360
+ uint_t[::] signal_
2181
2361
  list channels, pixels
2182
2362
  ssize_t c, i, h, count
2183
2363
 
@@ -2186,18 +2366,21 @@ def _flimlabs_signal(
2186
2366
  for channels in data:
2187
2367
  i = 0
2188
2368
  for pixels in channels:
2369
+ signal_ = signal[c, i]
2189
2370
  for h, count in pixels:
2190
- signal[c, i, h] = <uint_t> count
2371
+ signal_[h] = <uint_t> count
2191
2372
  i += 1
2192
2373
  c += 1
2193
2374
  else:
2194
2375
  i = 0
2195
2376
  for pixels in data[channel]:
2377
+ signal_ = signal[0, i]
2196
2378
  for h, count in pixels:
2197
- signal[0, i, h] = <uint_t> count
2379
+ signal_[h] = <uint_t> count
2198
2380
  i += 1
2199
2381
 
2200
2382
 
2383
+ @cython.boundscheck(True)
2201
2384
  def _flimlabs_mean(
2202
2385
  float_t[:, ::] mean, # channel, pixel
2203
2386
  list data, # list[list[list[[int, int]]]]
@@ -2205,6 +2388,7 @@ def _flimlabs_mean(
2205
2388
  ):
2206
2389
  """Return mean intensity image from FLIM LABS JSON intensity data."""
2207
2390
  cdef:
2391
+ float_t[::] mean_
2208
2392
  list channels, pixels
2209
2393
  ssize_t c, i, h, count
2210
2394
  double sum
@@ -2212,19 +2396,21 @@ def _flimlabs_mean(
2212
2396
  if channel < 0:
2213
2397
  c = 0
2214
2398
  for channels in data:
2399
+ mean_ = mean[c]
2215
2400
  i = 0
2216
2401
  for pixels in channels:
2217
2402
  sum = 0.0
2218
2403
  for h, count in pixels:
2219
2404
  sum += <double> count
2220
- mean[c, i] = <float_t> (sum / 255.0)
2405
+ mean_[i] = <float_t> (sum / 256.0)
2221
2406
  i += 1
2222
2407
  c += 1
2223
2408
  else:
2224
2409
  i = 0
2410
+ mean_ = mean[0]
2225
2411
  for pixels in data[channel]:
2226
2412
  sum = 0.0
2227
2413
  for h, count in pixels:
2228
2414
  sum += <double> count
2229
- mean[0, i] = <float_t> (sum / 255.0)
2415
+ mean_[i] = <float_t> (sum / 256.0)
2230
2416
  i += 1