tests.test_events

  1import os
  2import time
  3
  4import cv2
  5import numpy as np
  6import pandas as pd
  7
  8from csi_images.csi_scans import Scan
  9from csi_images.csi_tiles import Tile
 10from csi_images.csi_events import Event, EventArray
 11from csi_images import csi_images
 12
 13if os.environ.get("DEBIAN_FRONTEND") == "noninteractive":
 14    SHOW_PLOTS = False
 15else:
 16    # Change this to your preference for local testing, but commit as True
 17    SHOW_PLOTS = True
 18
 19
 20def test_getting_event():
 21    scan = Scan.load_txt("tests/data")
 22    tile = Tile(scan, 0)
 23    event = Event(
 24        scan,
 25        tile,
 26        516,
 27        278,
 28    )
 29    # Test getting images
 30    images = event.extract_images()
 31    assert len(images) == 4
 32    images = event.extract_images(crop_size=100)
 33    assert images[0].shape == (100, 100)
 34    images = event.extract_images(crop_size=50)
 35    assert images[0].shape == (50, 50)
 36
 37    if SHOW_PLOTS:
 38        for image in images:
 39            cv2.imshow("Bright DAPI event in the center", image)
 40            cv2.waitKey(0)
 41        cv2.destroyAllWindows()
 42
 43    # Test a corner event
 44    event = Event(
 45        scan,
 46        tile,
 47        1346,
 48        9,
 49    )
 50    images = event.extract_images()
 51    assert len(images) == 4
 52    images = event.extract_images(crop_size=100)
 53    assert images[0].shape == (100, 100)
 54    images = event.extract_images(crop_size=200)
 55    assert images[0].shape == (200, 200)
 56
 57    if SHOW_PLOTS:
 58        for image in images:
 59            cv2.imshow("Events in the top-right corner of a tile", image)
 60            cv2.waitKey(0)
 61        cv2.destroyAllWindows()
 62
 63
 64def test_getting_many_events():
 65    scan = Scan.load_txt("tests/data")
 66    tile = Tile(scan, 1000)
 67    tile2 = Tile(scan, 0)
 68    events = [
 69        Event(scan, tile, 515, 411),
 70        Event(scan, tile2, 2, 1000),
 71        Event(scan, tile, 1000, 1000),
 72        Event(scan, tile, 87, 126),
 73        Event(scan, tile, 1000, 2),
 74        Event(scan, tile2, 800, 800),
 75        Event(scan, tile, 1000, 662),
 76    ]
 77    # Test time to extract images sequentially
 78    start_time = time.time()
 79    images_1 = []
 80    for event in events:
 81        images_1.append(event.extract_images())
 82    sequential_time = time.time() - start_time
 83
 84    # Test time to extract images in parallel
 85    start_time = time.time()
 86    images_2 = Event.extract_images_for_list(events, crop_size=100)
 87    parallel_time = time.time() - start_time
 88    assert parallel_time < sequential_time
 89    for list_a, list_b in zip(images_1, images_2):
 90        assert len(list_a) == len(list_b)
 91        for image_a, image_b in zip(list_a, list_b):
 92            assert np.array_equal(image_a, image_b)
 93
 94    # Test that it works after converting to EventArray and back
 95    event_array = EventArray.from_events(events)
 96    remade_events = event_array.to_events(
 97        [scan], ignore_metadata=True, ignore_features=True
 98    )
 99    images_3 = Event.extract_images_for_list(remade_events, crop_size=100)
100    for list_a, list_b in zip(images_1, images_3):
101        assert len(list_a) == len(list_b)
102        for image_a, image_b in zip(list_a, list_b):
103            assert np.array_equal(image_a, image_b)
104
105
106def test_event_coordinates_for_bzscanner():
107    scan = Scan.load_txt("tests/data")
108    # Origin
109    tile = Tile(scan, 0)
110    event = Event(scan, tile, 0, 0)
111    scan_origin = event.get_scan_position()
112    assert 2500 <= scan_origin[0] <= 3500
113    assert 2500 <= scan_origin[1] <= 3500
114    scan_origin_on_slide = event.get_slide_position()
115    assert 71500 <= scan_origin_on_slide[0] <= 72500
116    assert 21500 <= scan_origin_on_slide[1] <= 22500
117    # Within the same tile, "bottom-right corner"
118    event = Event(scan, tile, 1000, 1000)
119    scan_position = event.get_scan_position()
120    assert scan_origin[0] <= scan_position[0]
121    assert scan_origin[1] <= scan_position[1]
122    slide_position = event.get_slide_position()
123    assert slide_position[0] <= scan_origin_on_slide[0]
124    assert slide_position[1] <= scan_origin_on_slide[1]
125
126    # Next row, opposite side
127    tile = Tile(scan, (scan.roi[0].tile_cols - 1, 1))
128    event = Event(scan, tile, 1000, 1000)
129    scan_position = event.get_scan_position()
130    assert scan_origin[0] <= scan_position[0]
131    assert scan_origin[1] <= scan_position[1]
132    slide_position = event.get_slide_position()
133    assert slide_position[0] <= scan_origin_on_slide[0]
134    assert slide_position[1] <= scan_origin_on_slide[1]
135
136    # Opposite corner
137    tile = Tile(scan, (scan.roi[0].tile_cols - 1, scan.roi[0].tile_rows - 1))
138    event = Event(scan, tile, 1361, 1003)
139    scan_position = event.get_scan_position()
140    assert 21500 <= scan_position[0] <= 22500
141    assert 58500 <= scan_position[1] <= 60500
142    slide_position = event.get_slide_position()
143    assert 14500 <= slide_position[0] <= 15500
144    assert 2500 <= slide_position[1] <= 3500
145
146
147def test_event_coordinates_for_axioscan():
148    scan = Scan.load_yaml("tests/data")
149    # Origin
150    tile = Tile(scan, 0)
151    event = Event(scan, tile, 0, 0)
152    scan_position = event.get_scan_position()
153    assert -59000 <= scan_position[0] < -55000
154    assert 0 <= scan_position[1] < 4000
155    slide_position = event.get_slide_position()
156    assert 16000 <= slide_position[0] < 20000
157    assert scan_position[1] == slide_position[1]
158
159    # Opposite corner
160    tile = Tile(scan, (scan.roi[0].tile_cols - 1, scan.roi[0].tile_rows - 1))
161    event = Event(scan, tile, 2000, 2000)
162    scan_position = event.get_scan_position()
163    assert -4000 <= scan_position[0] <= 0
164    assert 21000 <= scan_position[1] <= 25000
165    slide_position = event.get_slide_position()
166    assert 71000 <= slide_position[0] <= 75000
167    assert scan_position[1] == slide_position[1]
168
169
170def test_eventarray_conversions():
171    scan = Scan.load_yaml("tests/data")
172    # Origin
173    tile = Tile(scan, 0)
174    event0 = Event(scan, tile, 0, 0)
175    event1 = Event(scan, tile, 1000, 1000)
176    event2 = Event(scan, tile, 2000, 2000)
177
178    event_array = EventArray.from_events([event0, event1, event2])
179
180    assert len(event_array) == 3
181    assert event_array.metadata is None
182    assert event_array.features is None
183
184    event0.metadata = pd.Series({"event0": 0})
185
186    try:
187        event_array = EventArray.from_events([event0, event1, event2])
188        # Should throw error
189        assert False
190    except ValueError:
191        pass
192
193    event1.metadata = pd.Series({"event0": 1})
194    event2.metadata = pd.Series({"event0": 2})
195
196    event_array = EventArray.from_events([event0, event1, event2])
197
198    assert len(event_array) == 3
199
200    events_df = event_array.to_dataframe()
201
202    assert len(events_df) == 3
203
204    assert event_array == EventArray.from_dataframe(events_df)
205
206    # Test adding different dtypes and converting back and forth
207    event_array.features = pd.DataFrame(
208        {"feature1": [1, 2, 3], "feature2": [4.0, 5.0, 6.0]}
209    )
210    remade_event_list = event_array.to_events([scan])
211    assert len(remade_event_list) == 3
212    remade_event_array = EventArray.from_events(remade_event_list)
213    assert event_array == remade_event_array
214    # Test saving and loading
215    assert event_array.save_csv("tests/data/events.csv")
216    assert event_array == EventArray.load_csv("tests/data/events.csv")
217    os.remove("tests/data/events.csv")
218
219    assert event_array.save_hdf5("tests/data/events.h5")
220    assert event_array == EventArray.load_hdf5("tests/data/events.h5")
221    os.remove("tests/data/events.h5")
222
223
224def test_ocular_conversions():
225    scan = Scan.load_txt("tests/data")
226    input_path = "/mnt/HDSCA_Development/DZ/0B58703/ocular"
227    result = EventArray.load_ocular(input_path)
228    # For the purposes of this test, we will manually relabel "clust" == nan to 0
229    # These come from ocular_interesting.rds, which does not have clusters
230    result.metadata["clust"] = result.metadata["clust"].fillna(0)
231    result.metadata["hcpc"] = result.metadata["hcpc"].fillna(0)
232    result.save_ocular("tests/data")
233    new_result = EventArray.load_ocular("tests/data")
234    # # Sort them so that they are in the same order
235    result = result.sort(["tile", "x", "y"])
236    new_result = new_result.sort(["tile", "x", "y"])
237    # Note: hcpc method within ocularr and here are different
238    result.metadata["hcpc"] = new_result.metadata["hcpc"].copy()
239    assert result == new_result
240    # Clean up
241    os.remove("tests/data/rc-final.csv")
242    os.remove("tests/data/rc-final.rds")
243    os.remove("tests/data/rc-final1.rds")
244    os.remove("tests/data/rc-final2.rds")
245    os.remove("tests/data/rc-final3.rds")
246    os.remove("tests/data/rc-final4.rds")
247
248    # Try it with "others" files
249    result = EventArray.load_ocular(input_path, event_type="others")
250    result.save_ocular("tests/data", event_type="others")
251    new_result = EventArray.load_ocular("tests/data", event_type="others")
252    result = result.sort(["tile", "x", "y"])
253    new_result = new_result.sort(["tile", "x", "y"])
254    # Note: hcpc method within ocularr and here are different
255    result.metadata["hcpc"] = new_result.metadata["hcpc"].copy()
256    assert result == new_result
257    # Clean up
258    os.remove("tests/data/others-final.csv")
259    os.remove("tests/data/others-final.rds")
260    os.remove("tests/data/others-final1.rds")
261    os.remove("tests/data/others-final2.rds")
262    os.remove("tests/data/others-final3.rds")
263    os.remove("tests/data/others-final4.rds")
264
265
266def test_copy_sort_rows_get():
267    scan = Scan.load_yaml("tests/data")
268    # Origin
269    tile = Tile(scan, 0)
270    events = [
271        Event(scan, tile, 0, 100),
272        Event(scan, tile, 0, 0),
273        Event(scan, tile, 1000, 1000),
274        Event(scan, tile, 1000, 1),
275        Event(scan, tile, 2000, 2000),
276    ]
277
278    events = EventArray.from_events(events)
279
280    # Copy
281    events_copy = events.copy()
282    events_copy.info["x"] = np.uint16(1)
283    # Check that changes to the copy did not change the original
284    assert events_copy.info["x"].equals(pd.Series([1, 1, 1, 1, 1], dtype=np.uint16))
285    assert events.info["x"].equals(pd.Series([0, 0, 1000, 1000, 2000], dtype=np.uint16))
286
287    # Sort
288    events = events.sort(["x", "y"], ascending=[False, True])
289    assert events.info["x"].equals(pd.Series([2000, 1000, 1000, 0, 0], dtype=np.uint16))
290    assert events.info["y"].equals(pd.Series([2000, 1, 1000, 0, 100], dtype=np.uint16))
291
292    # Get
293    events_get = events.get(["x", "y"])
294    assert events_get["x"].equals(pd.Series([2000, 1000, 1000, 0, 0], dtype=np.uint16))
295    assert events_get["y"].equals(pd.Series([2000, 1, 1000, 0, 100], dtype=np.uint16))
296    assert events_get.columns.equals(pd.Index(["x", "y"]))
297
298    # Rows
299    events_get = events.rows([0, 1, 3])
300    assert len(events_get) == 3
301    assert events_get.info["x"].equals(pd.Series([2000, 1000, 0], dtype=np.uint16))
302    assert events_get.info["y"].equals(pd.Series([2000, 1, 0], dtype=np.uint16))
303    events_get = events.rows([True, False, False, True, True])
304    assert len(events_get) == 3
305    assert events_get.info["x"].equals(pd.Series([2000, 0, 0], dtype=np.uint16))
306    assert events_get.info["y"].equals(pd.Series([2000, 0, 100], dtype=np.uint16))
307
308
309def test_event_montages():
310    scan = Scan.load_txt("tests/data")
311    tile = Tile(scan, 1000)
312    event = Event(scan, tile, 515, 411)
313    images = event.extract_images(crop_size=100)
314
315    # Test different montages
316    montage = csi_images.make_montage(images, [0, 1, 2, 3])
317    montage_shape = csi_images.get_montage_shape(images[0].shape, 4)
318    assert montage.shape == montage_shape
319
320    if SHOW_PLOTS:
321        cv2.imshow("Horizontal montage with all channels", montage)
322        cv2.waitKey(0)
323        cv2.destroyAllWindows()
324
325    montage = csi_images.make_montage(images, [0, 2])
326    if SHOW_PLOTS:
327        cv2.imshow("Horizontal montage with DAPI, CD45", montage)
328        cv2.waitKey(0)
329        cv2.destroyAllWindows()
330
331    montage = csi_images.make_montage(images, [0, 2], horizontal=False)
332    if SHOW_PLOTS:
333        cv2.imshow("Vertical montage with DAPI, CD45", montage)
334        cv2.waitKey(0)
335        cv2.destroyAllWindows()
336
337    montage = csi_images.make_montage(
338        images,
339        [0, 2],
340        {0: (0, 0, 1), 2: (0, 1, 0)},
341    )
342    if SHOW_PLOTS:
343        cv2.imshow(
344            "Horizontal montage with DAPI+CD45, DAPI, CD45",
345            cv2.cvtColor(montage, cv2.COLOR_RGB2BGR),
346        )
347        cv2.waitKey(0)
348        cv2.destroyAllWindows()
349
350    montage = csi_images.make_montage(
351        images,
352        [0, 1, 2, 3],
353        {0: (0, 0, 1), 1: (1, 0, 0), 2: (0, 1, 0), 3: (1, 1, 1)},
354    )
355    if SHOW_PLOTS:
356        cv2.imshow(
357            "Full, classic montage",
358            cv2.cvtColor(montage, cv2.COLOR_RGB2BGR),
359        )
360        cv2.waitKey(0)
361        cv2.destroyAllWindows()
def test_getting_event():
21def test_getting_event():
22    scan = Scan.load_txt("tests/data")
23    tile = Tile(scan, 0)
24    event = Event(
25        scan,
26        tile,
27        516,
28        278,
29    )
30    # Test getting images
31    images = event.extract_images()
32    assert len(images) == 4
33    images = event.extract_images(crop_size=100)
34    assert images[0].shape == (100, 100)
35    images = event.extract_images(crop_size=50)
36    assert images[0].shape == (50, 50)
37
38    if SHOW_PLOTS:
39        for image in images:
40            cv2.imshow("Bright DAPI event in the center", image)
41            cv2.waitKey(0)
42        cv2.destroyAllWindows()
43
44    # Test a corner event
45    event = Event(
46        scan,
47        tile,
48        1346,
49        9,
50    )
51    images = event.extract_images()
52    assert len(images) == 4
53    images = event.extract_images(crop_size=100)
54    assert images[0].shape == (100, 100)
55    images = event.extract_images(crop_size=200)
56    assert images[0].shape == (200, 200)
57
58    if SHOW_PLOTS:
59        for image in images:
60            cv2.imshow("Events in the top-right corner of a tile", image)
61            cv2.waitKey(0)
62        cv2.destroyAllWindows()
def test_getting_many_events():
 65def test_getting_many_events():
 66    scan = Scan.load_txt("tests/data")
 67    tile = Tile(scan, 1000)
 68    tile2 = Tile(scan, 0)
 69    events = [
 70        Event(scan, tile, 515, 411),
 71        Event(scan, tile2, 2, 1000),
 72        Event(scan, tile, 1000, 1000),
 73        Event(scan, tile, 87, 126),
 74        Event(scan, tile, 1000, 2),
 75        Event(scan, tile2, 800, 800),
 76        Event(scan, tile, 1000, 662),
 77    ]
 78    # Test time to extract images sequentially
 79    start_time = time.time()
 80    images_1 = []
 81    for event in events:
 82        images_1.append(event.extract_images())
 83    sequential_time = time.time() - start_time
 84
 85    # Test time to extract images in parallel
 86    start_time = time.time()
 87    images_2 = Event.extract_images_for_list(events, crop_size=100)
 88    parallel_time = time.time() - start_time
 89    assert parallel_time < sequential_time
 90    for list_a, list_b in zip(images_1, images_2):
 91        assert len(list_a) == len(list_b)
 92        for image_a, image_b in zip(list_a, list_b):
 93            assert np.array_equal(image_a, image_b)
 94
 95    # Test that it works after converting to EventArray and back
 96    event_array = EventArray.from_events(events)
 97    remade_events = event_array.to_events(
 98        [scan], ignore_metadata=True, ignore_features=True
 99    )
100    images_3 = Event.extract_images_for_list(remade_events, crop_size=100)
101    for list_a, list_b in zip(images_1, images_3):
102        assert len(list_a) == len(list_b)
103        for image_a, image_b in zip(list_a, list_b):
104            assert np.array_equal(image_a, image_b)
def test_event_coordinates_for_bzscanner():
107def test_event_coordinates_for_bzscanner():
108    scan = Scan.load_txt("tests/data")
109    # Origin
110    tile = Tile(scan, 0)
111    event = Event(scan, tile, 0, 0)
112    scan_origin = event.get_scan_position()
113    assert 2500 <= scan_origin[0] <= 3500
114    assert 2500 <= scan_origin[1] <= 3500
115    scan_origin_on_slide = event.get_slide_position()
116    assert 71500 <= scan_origin_on_slide[0] <= 72500
117    assert 21500 <= scan_origin_on_slide[1] <= 22500
118    # Within the same tile, "bottom-right corner"
119    event = Event(scan, tile, 1000, 1000)
120    scan_position = event.get_scan_position()
121    assert scan_origin[0] <= scan_position[0]
122    assert scan_origin[1] <= scan_position[1]
123    slide_position = event.get_slide_position()
124    assert slide_position[0] <= scan_origin_on_slide[0]
125    assert slide_position[1] <= scan_origin_on_slide[1]
126
127    # Next row, opposite side
128    tile = Tile(scan, (scan.roi[0].tile_cols - 1, 1))
129    event = Event(scan, tile, 1000, 1000)
130    scan_position = event.get_scan_position()
131    assert scan_origin[0] <= scan_position[0]
132    assert scan_origin[1] <= scan_position[1]
133    slide_position = event.get_slide_position()
134    assert slide_position[0] <= scan_origin_on_slide[0]
135    assert slide_position[1] <= scan_origin_on_slide[1]
136
137    # Opposite corner
138    tile = Tile(scan, (scan.roi[0].tile_cols - 1, scan.roi[0].tile_rows - 1))
139    event = Event(scan, tile, 1361, 1003)
140    scan_position = event.get_scan_position()
141    assert 21500 <= scan_position[0] <= 22500
142    assert 58500 <= scan_position[1] <= 60500
143    slide_position = event.get_slide_position()
144    assert 14500 <= slide_position[0] <= 15500
145    assert 2500 <= slide_position[1] <= 3500
def test_event_coordinates_for_axioscan():
148def test_event_coordinates_for_axioscan():
149    scan = Scan.load_yaml("tests/data")
150    # Origin
151    tile = Tile(scan, 0)
152    event = Event(scan, tile, 0, 0)
153    scan_position = event.get_scan_position()
154    assert -59000 <= scan_position[0] < -55000
155    assert 0 <= scan_position[1] < 4000
156    slide_position = event.get_slide_position()
157    assert 16000 <= slide_position[0] < 20000
158    assert scan_position[1] == slide_position[1]
159
160    # Opposite corner
161    tile = Tile(scan, (scan.roi[0].tile_cols - 1, scan.roi[0].tile_rows - 1))
162    event = Event(scan, tile, 2000, 2000)
163    scan_position = event.get_scan_position()
164    assert -4000 <= scan_position[0] <= 0
165    assert 21000 <= scan_position[1] <= 25000
166    slide_position = event.get_slide_position()
167    assert 71000 <= slide_position[0] <= 75000
168    assert scan_position[1] == slide_position[1]
def test_eventarray_conversions():
171def test_eventarray_conversions():
172    scan = Scan.load_yaml("tests/data")
173    # Origin
174    tile = Tile(scan, 0)
175    event0 = Event(scan, tile, 0, 0)
176    event1 = Event(scan, tile, 1000, 1000)
177    event2 = Event(scan, tile, 2000, 2000)
178
179    event_array = EventArray.from_events([event0, event1, event2])
180
181    assert len(event_array) == 3
182    assert event_array.metadata is None
183    assert event_array.features is None
184
185    event0.metadata = pd.Series({"event0": 0})
186
187    try:
188        event_array = EventArray.from_events([event0, event1, event2])
189        # Should throw error
190        assert False
191    except ValueError:
192        pass
193
194    event1.metadata = pd.Series({"event0": 1})
195    event2.metadata = pd.Series({"event0": 2})
196
197    event_array = EventArray.from_events([event0, event1, event2])
198
199    assert len(event_array) == 3
200
201    events_df = event_array.to_dataframe()
202
203    assert len(events_df) == 3
204
205    assert event_array == EventArray.from_dataframe(events_df)
206
207    # Test adding different dtypes and converting back and forth
208    event_array.features = pd.DataFrame(
209        {"feature1": [1, 2, 3], "feature2": [4.0, 5.0, 6.0]}
210    )
211    remade_event_list = event_array.to_events([scan])
212    assert len(remade_event_list) == 3
213    remade_event_array = EventArray.from_events(remade_event_list)
214    assert event_array == remade_event_array
215    # Test saving and loading
216    assert event_array.save_csv("tests/data/events.csv")
217    assert event_array == EventArray.load_csv("tests/data/events.csv")
218    os.remove("tests/data/events.csv")
219
220    assert event_array.save_hdf5("tests/data/events.h5")
221    assert event_array == EventArray.load_hdf5("tests/data/events.h5")
222    os.remove("tests/data/events.h5")
def test_ocular_conversions():
225def test_ocular_conversions():
226    scan = Scan.load_txt("tests/data")
227    input_path = "/mnt/HDSCA_Development/DZ/0B58703/ocular"
228    result = EventArray.load_ocular(input_path)
229    # For the purposes of this test, we will manually relabel "clust" == nan to 0
230    # These come from ocular_interesting.rds, which does not have clusters
231    result.metadata["clust"] = result.metadata["clust"].fillna(0)
232    result.metadata["hcpc"] = result.metadata["hcpc"].fillna(0)
233    result.save_ocular("tests/data")
234    new_result = EventArray.load_ocular("tests/data")
235    # # Sort them so that they are in the same order
236    result = result.sort(["tile", "x", "y"])
237    new_result = new_result.sort(["tile", "x", "y"])
238    # Note: hcpc method within ocularr and here are different
239    result.metadata["hcpc"] = new_result.metadata["hcpc"].copy()
240    assert result == new_result
241    # Clean up
242    os.remove("tests/data/rc-final.csv")
243    os.remove("tests/data/rc-final.rds")
244    os.remove("tests/data/rc-final1.rds")
245    os.remove("tests/data/rc-final2.rds")
246    os.remove("tests/data/rc-final3.rds")
247    os.remove("tests/data/rc-final4.rds")
248
249    # Try it with "others" files
250    result = EventArray.load_ocular(input_path, event_type="others")
251    result.save_ocular("tests/data", event_type="others")
252    new_result = EventArray.load_ocular("tests/data", event_type="others")
253    result = result.sort(["tile", "x", "y"])
254    new_result = new_result.sort(["tile", "x", "y"])
255    # Note: hcpc method within ocularr and here are different
256    result.metadata["hcpc"] = new_result.metadata["hcpc"].copy()
257    assert result == new_result
258    # Clean up
259    os.remove("tests/data/others-final.csv")
260    os.remove("tests/data/others-final.rds")
261    os.remove("tests/data/others-final1.rds")
262    os.remove("tests/data/others-final2.rds")
263    os.remove("tests/data/others-final3.rds")
264    os.remove("tests/data/others-final4.rds")
def test_copy_sort_rows_get():
267def test_copy_sort_rows_get():
268    scan = Scan.load_yaml("tests/data")
269    # Origin
270    tile = Tile(scan, 0)
271    events = [
272        Event(scan, tile, 0, 100),
273        Event(scan, tile, 0, 0),
274        Event(scan, tile, 1000, 1000),
275        Event(scan, tile, 1000, 1),
276        Event(scan, tile, 2000, 2000),
277    ]
278
279    events = EventArray.from_events(events)
280
281    # Copy
282    events_copy = events.copy()
283    events_copy.info["x"] = np.uint16(1)
284    # Check that changes to the copy did not change the original
285    assert events_copy.info["x"].equals(pd.Series([1, 1, 1, 1, 1], dtype=np.uint16))
286    assert events.info["x"].equals(pd.Series([0, 0, 1000, 1000, 2000], dtype=np.uint16))
287
288    # Sort
289    events = events.sort(["x", "y"], ascending=[False, True])
290    assert events.info["x"].equals(pd.Series([2000, 1000, 1000, 0, 0], dtype=np.uint16))
291    assert events.info["y"].equals(pd.Series([2000, 1, 1000, 0, 100], dtype=np.uint16))
292
293    # Get
294    events_get = events.get(["x", "y"])
295    assert events_get["x"].equals(pd.Series([2000, 1000, 1000, 0, 0], dtype=np.uint16))
296    assert events_get["y"].equals(pd.Series([2000, 1, 1000, 0, 100], dtype=np.uint16))
297    assert events_get.columns.equals(pd.Index(["x", "y"]))
298
299    # Rows
300    events_get = events.rows([0, 1, 3])
301    assert len(events_get) == 3
302    assert events_get.info["x"].equals(pd.Series([2000, 1000, 0], dtype=np.uint16))
303    assert events_get.info["y"].equals(pd.Series([2000, 1, 0], dtype=np.uint16))
304    events_get = events.rows([True, False, False, True, True])
305    assert len(events_get) == 3
306    assert events_get.info["x"].equals(pd.Series([2000, 0, 0], dtype=np.uint16))
307    assert events_get.info["y"].equals(pd.Series([2000, 0, 100], dtype=np.uint16))
def test_event_montages():
310def test_event_montages():
311    scan = Scan.load_txt("tests/data")
312    tile = Tile(scan, 1000)
313    event = Event(scan, tile, 515, 411)
314    images = event.extract_images(crop_size=100)
315
316    # Test different montages
317    montage = csi_images.make_montage(images, [0, 1, 2, 3])
318    montage_shape = csi_images.get_montage_shape(images[0].shape, 4)
319    assert montage.shape == montage_shape
320
321    if SHOW_PLOTS:
322        cv2.imshow("Horizontal montage with all channels", montage)
323        cv2.waitKey(0)
324        cv2.destroyAllWindows()
325
326    montage = csi_images.make_montage(images, [0, 2])
327    if SHOW_PLOTS:
328        cv2.imshow("Horizontal montage with DAPI, CD45", montage)
329        cv2.waitKey(0)
330        cv2.destroyAllWindows()
331
332    montage = csi_images.make_montage(images, [0, 2], horizontal=False)
333    if SHOW_PLOTS:
334        cv2.imshow("Vertical montage with DAPI, CD45", montage)
335        cv2.waitKey(0)
336        cv2.destroyAllWindows()
337
338    montage = csi_images.make_montage(
339        images,
340        [0, 2],
341        {0: (0, 0, 1), 2: (0, 1, 0)},
342    )
343    if SHOW_PLOTS:
344        cv2.imshow(
345            "Horizontal montage with DAPI+CD45, DAPI, CD45",
346            cv2.cvtColor(montage, cv2.COLOR_RGB2BGR),
347        )
348        cv2.waitKey(0)
349        cv2.destroyAllWindows()
350
351    montage = csi_images.make_montage(
352        images,
353        [0, 1, 2, 3],
354        {0: (0, 0, 1), 1: (1, 0, 0), 2: (0, 1, 0), 3: (1, 1, 1)},
355    )
356    if SHOW_PLOTS:
357        cv2.imshow(
358            "Full, classic montage",
359            cv2.cvtColor(montage, cv2.COLOR_RGB2BGR),
360        )
361        cv2.waitKey(0)
362        cv2.destroyAllWindows()