spacr 0.0.2__py3-none-any.whl → 0.0.6__py3-none-any.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.
spacr/annotate_app.py CHANGED
@@ -13,38 +13,17 @@ from ttkthemes import ThemedTk
13
13
 
14
14
  from .logger import log_function_call
15
15
 
16
- from .gui_utils import ScrollableFrame, set_default_font, set_dark_style, create_dark_mode
16
+ from .gui_utils import ScrollableFrame, set_default_font, set_dark_style, create_dark_mode, style_text_boxes, create_menu_bar
17
17
 
18
18
  class ImageApp:
19
- """
20
- A class representing an image application.
21
-
22
- Attributes:
23
- - root (tkinter.Tk): The root window of the application.
24
- - db_path (str): The path to the SQLite database.
25
- - index (int): The index of the current page of images.
26
- - grid_rows (int): The number of rows in the image grid.
27
- - grid_cols (int): The number of columns in the image grid.
28
- - image_size (tuple): The size of the displayed images.
29
- - annotation_column (str): The column name for image annotations in the database.
30
- - image_type (str): The type of images to display.
31
- - channels (list): The channels to filter in the images.
32
- - images (dict): A dictionary mapping labels to loaded images.
33
- - pending_updates (dict): A dictionary of pending image annotation updates.
34
- - labels (list): A list of label widgets for displaying images.
35
- - terminate (bool): A flag indicating whether the application should terminate.
36
- - update_queue (Queue): A queue for storing image annotation updates.
37
- - status_label (tkinter.Label): A label widget for displaying status messages.
38
- - db_update_thread (threading.Thread): A thread for updating the database.
39
- """
40
-
41
- def _init_(self, root, db_path, image_type=None, channels=None, grid_rows=None, grid_cols=None, image_size=(200, 200), annotation_column='annotate'):
19
+ def __init__(self, root, db_path, src, image_type=None, channels=None, grid_rows=None, grid_cols=None, image_size=(200, 200), annotation_column='annotate'):
42
20
  """
43
21
  Initializes an instance of the ImageApp class.
44
22
 
45
23
  Parameters:
46
24
  - root (tkinter.Tk): The root window of the application.
47
25
  - db_path (str): The path to the SQLite database.
26
+ - src (str): The source directory that should be upstream of 'data' in the paths.
48
27
  - image_type (str): The type of images to display.
49
28
  - channels (list): The channels to filter in the images.
50
29
  - grid_rows (int): The number of rows in the image grid.
@@ -52,9 +31,9 @@ class ImageApp:
52
31
  - image_size (tuple): The size of the displayed images.
53
32
  - annotation_column (str): The column name for image annotations in the database.
54
33
  """
55
-
56
34
  self.root = root
57
35
  self.db_path = db_path
36
+ self.src = src
58
37
  self.index = 0
59
38
  self.grid_rows = grid_rows
60
39
  self.grid_cols = grid_cols
@@ -65,7 +44,7 @@ class ImageApp:
65
44
  self.images = {}
66
45
  self.pending_updates = {}
67
46
  self.labels = []
68
- #self.updating_db = True
47
+ self.adjusted_to_original_paths = {}
69
48
  self.terminate = False
70
49
  self.update_queue = Queue()
71
50
  self.status_label = Label(self.root, text="", font=("Arial", 12))
@@ -78,6 +57,68 @@ class ImageApp:
78
57
  label = Label(root)
79
58
  label.grid(row=i // grid_cols, column=i % grid_cols)
80
59
  self.labels.append(label)
60
+
61
+ def load_images(self):
62
+ """
63
+ Loads and displays images with annotations.
64
+
65
+ This method retrieves image paths and annotations from a SQLite database,
66
+ loads the images using a ThreadPoolExecutor for parallel processing,
67
+ adds colored borders to images based on their annotations,
68
+ and displays the images in the corresponding labels.
69
+
70
+ Args:
71
+ None
72
+
73
+ Returns:
74
+ None
75
+ """
76
+ for label in self.labels:
77
+ label.config(image='')
78
+
79
+ self.images = {}
80
+
81
+ conn = sqlite3.connect(self.db_path)
82
+ c = conn.cursor()
83
+ if self.image_type:
84
+ c.execute(f"SELECT png_path, {self.annotation_column} FROM png_list WHERE png_path LIKE ? LIMIT ?, ?", (f"%{self.image_type}%", self.index, self.grid_rows * self.grid_cols))
85
+ else:
86
+ c.execute(f"SELECT png_path, {self.annotation_column} FROM png_list LIMIT ?, ?", (self.index, self.grid_rows * self.grid_cols))
87
+
88
+ paths = c.fetchall()
89
+ conn.close()
90
+
91
+ adjusted_paths = []
92
+ for path, annotation in paths:
93
+ if not path.startswith(self.src):
94
+ parts = path.split('/data/')
95
+ if len(parts) > 1:
96
+ new_path = os.path.join(self.src, 'data', parts[1])
97
+ self.adjusted_to_original_paths[new_path] = path
98
+ adjusted_paths.append((new_path, annotation))
99
+ else:
100
+ adjusted_paths.append((path, annotation))
101
+ else:
102
+ adjusted_paths.append((path, annotation))
103
+
104
+ with ThreadPoolExecutor() as executor:
105
+ loaded_images = list(executor.map(self.load_single_image, adjusted_paths))
106
+
107
+ for i, (img, annotation) in enumerate(loaded_images):
108
+ if annotation:
109
+ border_color = 'teal' if annotation == 1 else 'red'
110
+ img = self.add_colored_border(img, border_width=5, border_color=border_color)
111
+
112
+ photo = ImageTk.PhotoImage(img)
113
+ label = self.labels[i]
114
+ self.images[label] = photo
115
+ label.config(image=photo)
116
+
117
+ path = adjusted_paths[i][0]
118
+ label.bind('<Button-1>', self.get_on_image_click(path, label, img))
119
+ label.bind('<Button-3>', self.get_on_image_click(path, label, img))
120
+
121
+ self.root.update()
81
122
 
82
123
  @staticmethod
83
124
  def normalize_image(img):
@@ -148,55 +189,6 @@ class ImageApp:
148
189
 
149
190
  return Image.merge("RGB", (r, g, b))
150
191
 
151
- def load_images(self):
152
- """
153
- Loads and displays images with annotations.
154
-
155
- This method retrieves image paths and annotations from a SQLite database,
156
- loads the images using a ThreadPoolExecutor for parallel processing,
157
- adds colored borders to images based on their annotations,
158
- and displays the images in the corresponding labels.
159
-
160
- Args:
161
- None
162
-
163
- Returns:
164
- None
165
- """
166
- for label in self.labels:
167
- label.config(image='')
168
-
169
- self.images = {}
170
-
171
- conn = sqlite3.connect(self.db_path)
172
- c = conn.cursor()
173
- if self.image_type:
174
- c.execute(f"SELECT png_path, {self.annotation_column} FROM png_list WHERE png_path LIKE ? LIMIT ?, ?", (f"%{self.image_type}%", self.index, self.grid_rows * self.grid_cols))
175
- else:
176
- c.execute(f"SELECT png_path, {self.annotation_column} FROM png_list LIMIT ?, ?", (self.index, self.grid_rows * self.grid_cols))
177
-
178
- paths = c.fetchall()
179
- conn.close()
180
-
181
- with ThreadPoolExecutor() as executor:
182
- loaded_images = list(executor.map(self.load_single_image, paths))
183
-
184
- for i, (img, annotation) in enumerate(loaded_images):
185
- if annotation:
186
- border_color = 'teal' if annotation == 1 else 'red'
187
- img = self.add_colored_border(img, border_width=5, border_color=border_color)
188
-
189
- photo = ImageTk.PhotoImage(img)
190
- label = self.labels[i]
191
- self.images[label] = photo
192
- label.config(image=photo)
193
-
194
- path = paths[i][0]
195
- label.bind('<Button-1>', self.get_on_image_click(path, label, img))
196
- label.bind('<Button-3>', self.get_on_image_click(path, label, img))
197
-
198
- self.root.update()
199
-
200
192
  def load_single_image(self, path_annotation_tuple):
201
193
  """
202
194
  Loads a single image from the given path and annotation tuple.
@@ -230,14 +222,15 @@ class ImageApp:
230
222
  function: The callback function for the image click event.
231
223
  """
232
224
  def on_image_click(event):
233
-
234
225
  new_annotation = 1 if event.num == 1 else (2 if event.num == 3 else None)
235
226
 
236
- if path in self.pending_updates and self.pending_updates[path] == new_annotation:
237
- self.pending_updates[path] = None
227
+ original_path = self.adjusted_to_original_paths.get(path, path)
228
+
229
+ if original_path in self.pending_updates and self.pending_updates[original_path] == new_annotation:
230
+ self.pending_updates[original_path] = None
238
231
  new_annotation = None
239
232
  else:
240
- self.pending_updates[path] = new_annotation
233
+ self.pending_updates[original_path] = new_annotation
241
234
 
242
235
  print(f"Image {os.path.split(path)[1]} annotated: {new_annotation}")
243
236
 
@@ -261,38 +254,38 @@ class ImageApp:
261
254
  """))
262
255
 
263
256
  def update_database_worker(self):
264
- """
265
- Worker function that continuously updates the database with pending updates from the update queue.
266
- It retrieves the pending updates from the queue, updates the corresponding records in the database,
267
- and resets the text in the HTML and status label.
268
- """
269
- conn = sqlite3.connect(self.db_path)
270
- c = conn.cursor()
271
-
272
- display(HTML("<div id='unique_id'>Initial Text</div>"))
273
-
274
- while True:
275
- if self.terminate:
276
- conn.close()
277
- break
278
-
279
- if not self.update_queue.empty():
280
- ImageApp.update_html("Do not exit, Updating database...")
281
- self.status_label.config(text='Do not exit, Updating database...')
282
-
283
- pending_updates = self.update_queue.get()
284
- for path, new_annotation in pending_updates.items():
285
- if new_annotation is None:
286
- c.execute(f'UPDATE png_list SET {self.annotation_column} = NULL WHERE png_path = ?', (path,))
287
- else:
288
- c.execute(f'UPDATE png_list SET {self.annotation_column} = ? WHERE png_path = ?', (new_annotation, path))
289
- conn.commit()
290
-
291
- # Reset the text
292
- ImageApp.update_html('')
293
- self.status_label.config(text='')
294
- self.root.update()
295
- time.sleep(0.1)
257
+ """
258
+ Worker function that continuously updates the database with pending updates from the update queue.
259
+ It retrieves the pending updates from the queue, updates the corresponding records in the database,
260
+ and resets the text in the HTML and status label.
261
+ """
262
+ conn = sqlite3.connect(self.db_path)
263
+ c = conn.cursor()
264
+
265
+ display(HTML("<div id='unique_id'>Initial Text</div>"))
266
+
267
+ while True:
268
+ if self.terminate:
269
+ conn.close()
270
+ break
271
+
272
+ if not self.update_queue.empty():
273
+ ImageApp.update_html("Do not exit, Updating database...")
274
+ self.status_label.config(text='Do not exit, Updating database...')
275
+
276
+ pending_updates = self.update_queue.get()
277
+ for path, new_annotation in pending_updates.items():
278
+ if new_annotation is None:
279
+ c.execute(f'UPDATE png_list SET {self.annotation_column} = NULL WHERE png_path = ?', (path,))
280
+ else:
281
+ c.execute(f'UPDATE png_list SET {self.annotation_column} = ? WHERE png_path = ?', (new_annotation, path))
282
+ conn.commit()
283
+
284
+ # Reset the text
285
+ ImageApp.update_html('')
286
+ self.status_label.config(text='')
287
+ self.root.update()
288
+ time.sleep(0.1)
296
289
 
297
290
  def update_gui_text(self, text):
298
291
  """
@@ -356,12 +349,13 @@ class ImageApp:
356
349
  self.root.destroy()
357
350
  print(f'Quit application')
358
351
 
359
- def annotate(db, image_type=None, channels=None, geom="1000x1100", img_size=(200, 200), rows=5, columns=5, annotation_column='annotate'):
352
+ def annotate(src, image_type=None, channels=None, geom="1000x1100", img_size=(200, 200), rows=5, columns=5, annotation_column='annotate'):
360
353
  """
361
354
  Annotates images in a database using a graphical user interface.
362
355
 
363
356
  Args:
364
357
  db (str): The path to the SQLite database.
358
+ src (str): The source directory that should be upstream of 'data' in the paths.
365
359
  image_type (str, optional): The type of images to load from the database. Defaults to None.
366
360
  channels (str, optional): The channels of the images to load from the database. Defaults to None.
367
361
  geom (str, optional): The geometry of the GUI window. Defaults to "1000x1100".
@@ -370,7 +364,10 @@ def annotate(db, image_type=None, channels=None, geom="1000x1100", img_size=(200
370
364
  columns (int, optional): The number of columns in the image grid. Defaults to 5.
371
365
  annotation_column (str, optional): The name of the annotation column in the database table. Defaults to 'annotate'.
372
366
  """
373
- #display(HTML("<div id='unique_id'>Initial Text</div>"))
367
+ db = os.path.join(src, 'measurements/measurements.db')
368
+ #print('src', src)
369
+ #print('db', db)
370
+
374
371
  conn = sqlite3.connect(db)
375
372
  c = conn.cursor()
376
373
  c.execute('PRAGMA table_info(png_list)')
@@ -382,8 +379,7 @@ def annotate(db, image_type=None, channels=None, geom="1000x1100", img_size=(200
382
379
 
383
380
  root = tk.Tk()
384
381
  root.geometry(geom)
385
- app = ImageApp(root, db, image_type=image_type, channels=channels, image_size=img_size, grid_rows=rows, grid_cols=columns, annotation_column=annotation_column)
386
-
382
+ app = ImageApp(root, db, src, image_type=image_type, channels=channels, image_size=img_size, grid_rows=rows, grid_cols=columns, annotation_column=annotation_column)
387
383
  next_button = tk.Button(root, text="Next", command=app.next_page)
388
384
  next_button.grid(row=app.grid_rows, column=app.grid_cols - 1)
389
385
  back_button = tk.Button(root, text="Back", command=app.previous_page)
@@ -394,6 +390,7 @@ def annotate(db, image_type=None, channels=None, geom="1000x1100", img_size=(200
394
390
  app.load_images()
395
391
  root.mainloop()
396
392
 
393
+
397
394
  def check_for_duplicates(db):
398
395
  """
399
396
  Check for duplicates in the given SQLite database.
@@ -419,13 +416,14 @@ def check_for_duplicates(db):
419
416
  conn.commit()
420
417
  conn.close()
421
418
 
422
- @log_function_call
419
+ #@log_function_call
423
420
  def initiate_annotation_app_root(width, height):
424
421
  theme = 'breeze'
425
422
  root = ThemedTk(theme=theme)
426
423
  style = ttk.Style(root)
427
424
  set_dark_style(style)
428
- set_default_font(root, font_name="Arial", size=10)
425
+ style_text_boxes(style)
426
+ set_default_font(root, font_name="Arial", size=8)
429
427
  root.geometry(f"{width}x{height}")
430
428
  root.title("Annotation App")
431
429
 
@@ -473,6 +471,7 @@ def initiate_annotation_app_root(width, height):
473
471
  new_root = tk.Tk()
474
472
  new_root.geometry(f"{width}x{height}")
475
473
  new_root.title("Mask Application")
474
+
476
475
 
477
476
  # Start the annotation application in the new root window
478
477
  app_instance = annotate(db, image_type, channels, annotation_column, geom, img_size, rows, columns)
@@ -482,7 +481,7 @@ def initiate_annotation_app_root(width, height):
482
481
  create_dark_mode(root, style, console_output=None)
483
482
 
484
483
  run_button = ttk.Button(scrollable_frame.scrollable_frame, text="Run", command=run_app)
485
- run_button.grid(row=row, column=0, columnspan=2, pady=10)
484
+ run_button.grid(row=row, column=0, columnspan=2, pady=10, padx=10)
486
485
 
487
486
  return root
488
487
 
@@ -491,5 +490,4 @@ def gui_annotation():
491
490
  root.mainloop()
492
491
 
493
492
  if __name__ == "__main__":
494
- gui_annotation()
495
-
493
+ gui_annotation()
spacr/chris.py ADDED
@@ -0,0 +1,50 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+ from .core import _permutation_importance, _shap_analysis
4
+
5
+ def join_measurments_and_annotation(src, tables = ['cell', 'nucleus', 'pathogen','cytoplasm']):
6
+
7
+ from .io import _read_and_merge_data, _read_db
8
+
9
+ db_loc = [src+'/measurements/measurements.db']
10
+ loc = src+'/measurements/measurements.db'
11
+ df, _ = _read_and_merge_data(db_loc,
12
+ tables,
13
+ verbose=True,
14
+ include_multinucleated=True,
15
+ include_multiinfected=True,
16
+ include_noninfected=True)
17
+
18
+ paths_df = _read_db(loc, tables=['png_list'])
19
+
20
+ merged_df = pd.merge(df, paths_df[0], on='prcfo', how='left')
21
+
22
+ return merged_df
23
+
24
+ def plate_heatmap(src, model_type='xgboost', variable='predictions', grouping='mean', min_max='allq', cmap='viridis', channel_of_interest=3, min_count=25, n_estimators=100, col_to_compare='col', pos='c1', neg='c2', exclude=None, n_repeats=10, clean=True, nr_to_plot=20, verbose=False, n_jobs=-1):
25
+ from .io import _read_and_merge_data
26
+ from .plot import _plot_plates
27
+
28
+ db_loc = [src+'/measurements/measurements.db']
29
+ tables = ['cell', 'nucleus', 'pathogen','cytoplasm']
30
+ include_multinucleated, include_multiinfected, include_noninfected = True, 2.0, True
31
+
32
+ df = join_measurments_and_annotation(src, tables=['cell', 'nucleus', 'pathogen', 'cytoplasm'])
33
+
34
+ if not channel_of_interest is None:
35
+ df['recruitment'] = df[f'pathogen_channel_{channel_of_interest}_mean_intensity']/df[f'cytoplasm_channel_{channel_of_interest}_mean_intensity']
36
+ feature_string = f'channel_{channel_of_interest}'
37
+ else:
38
+ feature_string = None
39
+
40
+ output = _permutation_importance(df, feature_string, col_to_compare, pos, neg, exclude, n_repeats, clean, nr_to_plot, n_estimators=n_estimators, random_state=42, model_type=model_type, n_jobs=n_jobs)
41
+
42
+ _shap_analysis(output[3], output[4], output[5])
43
+
44
+ features = output[0].select_dtypes(include=[np.number]).columns.tolist()
45
+
46
+ if not variable in features:
47
+ raise ValueError(f"Variable {variable} not found in the dataframe. Please choose one of the following: {features}")
48
+
49
+ plate_heatmap = _plot_plates(output[0], variable, grouping, min_max, cmap, min_count)
50
+ return [output, plate_heatmap]