batoolset 0.1.0__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.
Files changed (162) hide show
  1. batoolset/GUI/__init__.py +0 -0
  2. batoolset/GUI/list_gui.py +513 -0
  3. batoolset/GUI/multi_image_display.py +989 -0
  4. batoolset/GUI/open.py +325 -0
  5. batoolset/GUI/overlay_hints.py +45 -0
  6. batoolset/GUI/paint2.py +1345 -0
  7. batoolset/GUI/scrollablepaint.py +1152 -0
  8. batoolset/IJ_ROIs/__init__.py +0 -0
  9. batoolset/IJ_ROIs/tools.py +40 -0
  10. batoolset/PATH/__init__.py +0 -0
  11. batoolset/PATH/tools.py +79 -0
  12. batoolset/SQLite_tools/__init__.py +0 -0
  13. batoolset/SQLite_tools/tools.py +840 -0
  14. batoolset/__init__.py +0 -0
  15. batoolset/binary/__init__.py +0 -0
  16. batoolset/binary/tools.py +123 -0
  17. batoolset/binarytools/__init__.py +0 -0
  18. batoolset/binarytools/cell_center_detector.py +290 -0
  19. batoolset/clipboard_tools/__init__.py +0 -0
  20. batoolset/clipboard_tools/tools.py +7 -0
  21. batoolset/colors/__init__.py +0 -0
  22. batoolset/colors/colorgen.py +193 -0
  23. batoolset/colors/tools.py +22 -0
  24. batoolset/command_line/__init__.py +0 -0
  25. batoolset/command_line/tools.py +18 -0
  26. batoolset/dialogs/__init__.py +0 -0
  27. batoolset/dialogs/opensave.py +228 -0
  28. batoolset/dicts/__init__.py +0 -0
  29. batoolset/dicts/tools.py +299 -0
  30. batoolset/dims/__init__.py +0 -0
  31. batoolset/dims/tools.py +83 -0
  32. batoolset/draw/__init__.py +0 -0
  33. batoolset/draw/shapes/Position.py +183 -0
  34. batoolset/draw/shapes/__init__.py +0 -0
  35. batoolset/draw/shapes/automated_alignment_drawing_refactored.py +346 -0
  36. batoolset/draw/shapes/circle2d.py +99 -0
  37. batoolset/draw/shapes/compute_optimal_size_all_merged.py +317 -0
  38. batoolset/draw/shapes/ellipse2d.py +336 -0
  39. batoolset/draw/shapes/freehand2d.py +235 -0
  40. batoolset/draw/shapes/group.py +1830 -0
  41. batoolset/draw/shapes/image2d.py +2469 -0
  42. batoolset/draw/shapes/line2d.py +575 -0
  43. batoolset/draw/shapes/point2d.py +257 -0
  44. batoolset/draw/shapes/polygon2d.py +357 -0
  45. batoolset/draw/shapes/polyline2d.py +235 -0
  46. batoolset/draw/shapes/rect2d.py +388 -0
  47. batoolset/draw/shapes/rectangle2d.py +601 -0
  48. batoolset/draw/shapes/scalebar.py +304 -0
  49. batoolset/draw/shapes/serializablefont.py +126 -0
  50. batoolset/draw/shapes/serializableguiparams.py +10 -0
  51. batoolset/draw/shapes/square2d.py +103 -0
  52. batoolset/draw/shapes/textorientation.py +45 -0
  53. batoolset/draw/shapes/tools.py +467 -0
  54. batoolset/draw/shapes/transform2D.py +350 -0
  55. batoolset/draw/shapes/txt2d.py +550 -0
  56. batoolset/draw/widgets/__init__.py +0 -0
  57. batoolset/draw/widgets/col.py +343 -0
  58. batoolset/draw/widgets/image_group.py +248 -0
  59. batoolset/draw/widgets/image_organizer.py +96 -0
  60. batoolset/draw/widgets/paint.py +184 -0
  61. batoolset/draw/widgets/vectorial.py +257 -0
  62. batoolset/figure/__init__.py +0 -0
  63. batoolset/figure/alignment.py +814 -0
  64. batoolset/figure/column.py +1117 -0
  65. batoolset/figure/fig_tools.py +285 -0
  66. batoolset/figure/minimal_debugger_scale_etc.py +139 -0
  67. batoolset/figure/panel.py +697 -0
  68. batoolset/figure/row.py +852 -0
  69. batoolset/figure/user_selection.py +287 -0
  70. batoolset/files/__init__.py +0 -0
  71. batoolset/files/tools.py +1321 -0
  72. batoolset/images/__init__.py +0 -0
  73. batoolset/images/tools.py +81 -0
  74. batoolset/img.py +5216 -0
  75. batoolset/linux/__init__.py +0 -0
  76. batoolset/linux/tools.py +72 -0
  77. batoolset/lists/__init__.py +0 -0
  78. batoolset/lists/tools.py +436 -0
  79. batoolset/luts/__init__.py +0 -0
  80. batoolset/luts/lut_minimal_test.py +1207 -0
  81. batoolset/matplolib/__init__.py +0 -0
  82. batoolset/matplolib/tools.py +44 -0
  83. batoolset/np_tools/__init__.py +0 -0
  84. batoolset/np_tools/tools.py +252 -0
  85. batoolset/np_tools/trash.py +15 -0
  86. batoolset/pandas_tools/__init__.py +0 -0
  87. batoolset/pandas_tools/tools.py +880 -0
  88. batoolset/partials/__init__.py +0 -0
  89. batoolset/partials/tools.py +83 -0
  90. batoolset/plot_tools/__init__.py +0 -0
  91. batoolset/plot_tools/tools.py +70 -0
  92. batoolset/pyqt/__init__.py +0 -0
  93. batoolset/pyqt/tools.py +691 -0
  94. batoolset/re_tools/__init__.py +0 -0
  95. batoolset/re_tools/tools.py +35 -0
  96. batoolset/serialization/__init__.py +0 -0
  97. batoolset/serialization/tools.py +1192 -0
  98. batoolset/sets/__init__.py +0 -0
  99. batoolset/sets/tools.py +109 -0
  100. batoolset/settings/__init__.py +0 -0
  101. batoolset/settings/global_settings.py +134 -0
  102. batoolset/strings/__init__.py +0 -0
  103. batoolset/strings/tools.py +225 -0
  104. batoolset/ta/__init__.py +0 -0
  105. batoolset/ta/database/__init__.py +0 -0
  106. batoolset/ta/database/advanced_sql_plotter.py +1170 -0
  107. batoolset/ta/database/my_qt_table.py +79 -0
  108. batoolset/ta/database/preview_and_edit_sqlite_db.py +405 -0
  109. batoolset/ta/database/sql.py +2141 -0
  110. batoolset/ta/database/sql_column_name_dialog.py +122 -0
  111. batoolset/ta/measurements/TAmeasures.py +2226 -0
  112. batoolset/ta/measurements/__init__.py +0 -0
  113. batoolset/ta/measurements/measurements3D/__init__.py +0 -0
  114. batoolset/ta/measurements/measurements3D/get_point_on_surface_if_centroid_is_bad.py +175 -0
  115. batoolset/ta/measurements/measurements3D/height_map_to_projection.py +82 -0
  116. batoolset/ta/measurements/measurements3D/measures3D.py +208 -0
  117. batoolset/ta/measurements/measurements3D/triangulate.py +331 -0
  118. batoolset/ta/measurements/nematic.py +562 -0
  119. batoolset/ta/measurements/nematic_measurements.py +107 -0
  120. batoolset/ta/segmentation/__init__.py +0 -0
  121. batoolset/ta/segmentation/neo_wshed.py +314 -0
  122. batoolset/ta/selections/__init__.py +0 -0
  123. batoolset/ta/selections/selection.py +329 -0
  124. batoolset/ta/tracking/__init__.py +0 -0
  125. batoolset/ta/tracking/last_tracking_based_on_matching_or_on_translation_from_mermaid_warp.py +653 -0
  126. batoolset/ta/tracking/local_to_track_correspondance.py +138 -0
  127. batoolset/ta/tracking/rapid_detection_of_vertices.py +517 -0
  128. batoolset/ta/tracking/registration.py +108 -0
  129. batoolset/ta/tracking/swap_detector_and_fixer.py +296 -0
  130. batoolset/ta/tracking/track_correction.py +238 -0
  131. batoolset/ta/tracking/tracking_error_detector_and_fixer.py +3099 -0
  132. batoolset/ta/tracking/tracking_yet_another_approach_pyramidal_registration_n_neo_swapping_correction.py +725 -0
  133. batoolset/ta/utils/TA_tools.py +43 -0
  134. batoolset/ta/utils/__init__.py +0 -0
  135. batoolset/ta/utils/rps_tools.py +1783 -0
  136. batoolset/tests/__init__.py +0 -0
  137. batoolset/tests/rps_tools.py +51 -0
  138. batoolset/tests/tools.py +10 -0
  139. batoolset/tools/__init__.py +0 -0
  140. batoolset/tools/early_stopper_class.py +9 -0
  141. batoolset/tools/guided_data_recoverer.py +110 -0
  142. batoolset/tools/logger.py +100 -0
  143. batoolset/tools/mytimer.py +31 -0
  144. batoolset/tools/prompt_color.py +160 -0
  145. batoolset/tools/qthandler.py +118 -0
  146. batoolset/uitools/__init__.py +0 -0
  147. batoolset/uitools/blinker.py +56 -0
  148. batoolset/unsafe/__init__.py +0 -0
  149. batoolset/unsafe/tools.py +115 -0
  150. batoolset/utils/__init__.py +0 -0
  151. batoolset/utils/commontools.py +158 -0
  152. batoolset/utils/loadlist.py +728 -0
  153. batoolset/version.py +10 -0
  154. batoolset/xml/__init__.py +0 -0
  155. batoolset/xml/tools.py +65 -0
  156. batoolset/zip_tools/__init__.py +0 -0
  157. batoolset/zip_tools/tools.py +76 -0
  158. batoolset-0.1.0.dist-info/LICENSE +24 -0
  159. batoolset-0.1.0.dist-info/METADATA +45 -0
  160. batoolset-0.1.0.dist-info/RECORD +162 -0
  161. batoolset-0.1.0.dist-info/WHEEL +5 -0
  162. batoolset-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,2141 @@
1
+ import sqlite3
2
+ import traceback
3
+ import math
4
+ import pandas
5
+ from prettytable import PrettyTable
6
+ import batoolset.SQLite_tools.tools
7
+ from batoolset.img import Img, has_metadata, _create_dir
8
+ from batoolset.ta.selections.selection import convert_coords_to_IDs
9
+ from batoolset.tools.early_stopper_class import early_stop
10
+ from batoolset.tools.logger import TA_logger # logging
11
+ from batoolset.utils.loadlist import smart_TA_list,loadlist
12
+ from batoolset.files.tools import smart_name_parser
13
+ from pathlib import Path
14
+ import pandas as pd
15
+ import os
16
+
17
+ logger = TA_logger()
18
+
19
+ # " need header"
20
+ def create_table_and_append_data(db_path, table_name, columns, data, column_types=None, temporary=False):
21
+ """
22
+ Creates an SQL table in a database and fills it with data.
23
+
24
+ Args:
25
+ db_path (str): The path to the database file.
26
+ table_name (str): The name of the table to create.
27
+ columns (list): A list of column names.
28
+ data (dict): A dictionary containing the data to populate the table, where the keys represent column names and the values represent column data.
29
+ column_types (list, optional): A list of column types. Defaults to None.
30
+ temporary (bool, optional): Specifies whether the created table is temporary. Defaults to False.
31
+
32
+ Raises:
33
+ Exception: If an error occurs while creating the table or filling it with data, or when closing the database connection.
34
+
35
+ Examples:
36
+ # >>> columns = ['id', 'name', 'age']
37
+ # >>> data = {
38
+ # ... 'id': [1, 2, 3],
39
+ # ... 'name': ['John', 'Jane', 'Alice'],
40
+ # ... 'age': [25, 32, 41]
41
+ # ... }
42
+ # >>> create_table_and_append_data("mydb.db", "persons", columns, data)
43
+ #
44
+ # >>> columns = ['id', 'fruit']
45
+ # >>> data = {
46
+ # ... 'id': [1, 2, 3],
47
+ # ... 'fruit': ['apple', 'banana', 'cherry']
48
+ # ... }
49
+ # >>> column_types = ['INTEGER', 'TEXT']
50
+ # >>> create_table_and_append_data("mydb.db", "fruits", columns, data, column_types=column_types, temporary=True)
51
+
52
+ # >>> columns = ['id', 'fruit']
53
+ # >>> data = np.array( [['1', 'apple'], ['2', 'banana'], ['3', 'cherry']])
54
+ # >>> column_types = ['INTEGER', 'TEXT']
55
+ # >>> create_table_and_append_data("mydb.db", "fruits", columns, data, column_types=column_types, temporary=False)
56
+ """
57
+
58
+ db = None
59
+
60
+ try:
61
+ db = TAsql(db_path)
62
+
63
+ # Drop the table if it already exists
64
+ db.drop_table(table_name)
65
+
66
+ # Prepare the column content for the CREATE TABLE statement
67
+ col_content = columns
68
+ if isinstance(col_content, list):
69
+ if column_types is not None:
70
+ # Combine the column names and types
71
+ concat = list(zip(columns, column_types))
72
+ concat = [str(name) + ' ' + str(type) for name, type in concat]
73
+ col_content = _list_to_string(concat, add_quotes=False)
74
+ else:
75
+ col_content = _list_to_string(col_content, add_quotes=False)
76
+
77
+ # Create the table with the specified columns and column types
78
+ db.cur.execute(
79
+ 'CREATE' + (' TEMPORARY' if temporary else '') + ' TABLE ' + table_name + ' (' + col_content + ')')
80
+ db.fill_table(table_name, data)
81
+
82
+ except:
83
+ traceback.print_exc()
84
+ logger.error('Something went wrong...')
85
+
86
+ finally:
87
+ if db is not None:
88
+ try:
89
+ db.close()
90
+ except:
91
+ traceback.print_exc()
92
+
93
+ def get_table_columns(path_to_db, table_name):
94
+ """
95
+ Given a table name, prints the names of all the columns in the table.
96
+
97
+ Args:
98
+ path_to_db (str): The path to the SQLite database file.
99
+ table_name (str): The name of the table.
100
+
101
+ Returns:
102
+ None
103
+
104
+ Examples:
105
+ >>> get_table_columns('/media/teamPrudhomme/EqpPrudhomme2/Benoit_pr_Benjamin/coccinelles/latest images_20230614/raw images/N4N4_29_M_1a/ladybug_seg.db', 'elytras_shape')[:2]
106
+ ['areas_without_holes', 'areas_with_holes']
107
+ """
108
+ return batoolset.SQLite_tools.tools.get_table_columns(path_to_db, table_name)
109
+
110
+
111
+ def remove_dupes_from_table_and_overwrite_table(db_path, table_name):
112
+ """
113
+ Removes duplicates from a table and overwrites the table with the deduplicated data.
114
+
115
+ Args:
116
+ db_path (str): The path to the database file.
117
+ table_name (str): The name of the table to deduplicate.
118
+
119
+ Returns:
120
+ list: The query results.
121
+
122
+ # Examples:
123
+ # >>> remove_dupes_from_table_and_overwrite_table('database.db', 'my_table')
124
+ # [1, 2, 3, 4]
125
+ """
126
+
127
+ query_results = []
128
+ db = None
129
+
130
+ try:
131
+ db = TAsql(db_path)
132
+
133
+ headers, data_rows = db.run_SQL_command_and_get_results('SELECT DISTINCT * FROM ' + table_name, return_header=True)
134
+
135
+ if not isinstance(data_rows, np.ndarray):
136
+ data_rows = np.asarray(data_rows, dtype=object)
137
+ data_rows = data_rows.T
138
+ finally_formatted_table = {}
139
+
140
+ for iii, header in enumerate(headers):
141
+ try:
142
+ finally_formatted_table[header] = data_rows[iii].tolist()
143
+ except:
144
+ logger.warning('no data to be added --> the column will be empty')
145
+ finally_formatted_table[header] = []
146
+
147
+ db.drop_table(table_name)
148
+ db.create_and_append_table(table_name, finally_formatted_table)
149
+
150
+ except:
151
+ traceback.print_exc()
152
+ logger.error('Something went wrong...')
153
+
154
+ finally:
155
+ if db is not None:
156
+ try:
157
+ db.close()
158
+ except:
159
+ traceback.print_exc()
160
+
161
+ return query_results
162
+
163
+
164
+ # maybe put this some other place --> this saves a table data and header as csv
165
+
166
+ def save_data_to_csv(output_file_name, header, data):
167
+ """
168
+ Saves data to a CSV file.
169
+
170
+ Args:
171
+ output_file_name (str): The name of the output CSV file.
172
+ header (list): The list of column headers.
173
+ data (list): The list of data rows.
174
+
175
+ # Examples:
176
+ # >>> header = ['Name', 'Age', 'Gender']
177
+ # >>> data = [['John', 25, 'Male'], ['Jane', 30, 'Female']]
178
+ # >>> save_data_to_csv('output.csv', header, data)
179
+ """
180
+
181
+ _create_dir(output_file_name) # Create directory if it doesn't exist
182
+
183
+ if header is not None and header: # Check if header is provided and not empty
184
+ db_df = pd.DataFrame(data=data if data else None, columns=header) # Create a DataFrame with the data and header
185
+ db_df.to_csv(output_file_name, index=False) # Save the DataFrame to a CSV file without index
186
+ else:
187
+ open(output_file_name, 'w').close() # Create an empty file if no header is provided
188
+
189
+
190
+ def prepend_to_content(content, thing_to_prepend, auto_convert_tuple_to_list=True):
191
+ """
192
+ Prepends a value to each element in a nested list or a single list.
193
+
194
+ Args:
195
+ content (list or tuple): The content to prepend the value to.
196
+ thing_to_prepend (any): The value to prepend.
197
+ auto_convert_tuple_to_list (bool): Flag to automatically convert tuples to lists.
198
+
199
+ Returns:
200
+ list or tuple: The modified content with the value prepended.
201
+
202
+ Examples:
203
+ >>> content = [['Apple', 'Banana'], ['Orange', 'Mango']]
204
+ >>> prepend_to_content(content, 'Fruit')
205
+ [['Fruit', 'Apple', 'Banana'], ['Fruit', 'Orange', 'Mango']]
206
+ """
207
+
208
+ if isinstance(content, list) and content: # Check if content is a non-empty list
209
+ if isinstance(content[0], (list, tuple)): # Check if content is a nested list or tuple
210
+ for iii, cur_content in enumerate(content):
211
+ if auto_convert_tuple_to_list and isinstance(cur_content, tuple):
212
+ cur_content = list(cur_content) # Convert tuple to list if auto conversion is enabled
213
+ cur_content.insert(0, thing_to_prepend) # Prepend the value to each element
214
+ content[iii] = cur_content # Update the modified element in the content list
215
+ else:
216
+ if auto_convert_tuple_to_list and isinstance(content, tuple):
217
+ content = list(content) # Convert tuple to list if auto conversion is enabled
218
+ content.insert(0, thing_to_prepend) # Prepend the value to the content list
219
+ return content
220
+
221
+ def query_db_and_get_results(db_path, SQL_command_to_run):
222
+ """
223
+ Executes an SQL command on a database and retrieves the query results.
224
+
225
+ Args:
226
+ db_path (str): The path to the database.
227
+ SQL_command_to_run (str): The SQL command to execute.
228
+
229
+ Returns:
230
+ list: The results of the SQL query.
231
+
232
+ # Examples:
233
+ # >>> query_db_and_get_results('database.db', 'SELECT * FROM customers')
234
+ # [['John', 'Doe', 'john.doe@example.com'], ['Jane', 'Smith', 'jane.smith@example.com']]
235
+ """
236
+
237
+ from batoolset.ta.database.sql import TAsql # Import the required module
238
+ query_results = [] # Initialize an empty list to store the query results
239
+ db = None
240
+
241
+ try:
242
+ db = TAsql(db_path) # Create a TAsql object with the specified database path
243
+ query_results = db.run_SQL_command_and_get_results(SQL_command_to_run, return_header=False)
244
+ # Execute the SQL command and retrieve the results
245
+ except:
246
+ traceback.print_exc() # Print the traceback in case of an exception
247
+ logger.error('Something went wrong...') # Log an error message
248
+ finally:
249
+ if db is not None:
250
+ try:
251
+ db.close() # Close the database connection
252
+ except:
253
+ traceback.print_exc() # Print the traceback in case of an exception
254
+ return query_results # Return the query results
255
+
256
+ def table_exists_in_db(db_path, table_name):
257
+ """
258
+ Checks if a table exists in a database.
259
+
260
+ Args:
261
+ db_path (str): The path to the database.
262
+ table_name (str): The name of the table to check.
263
+
264
+ Returns:
265
+ bool: True if the table exists, False otherwise.
266
+
267
+ # Examples:
268
+ # >>> table_exists_in_db('database.db', 'customers')
269
+ # True
270
+ """
271
+ from batoolset.ta.database.sql import TAsql
272
+ db = None
273
+ exists = False
274
+ try:
275
+ db = TAsql(db_path)
276
+ exists = db.exists(table_name)
277
+ except:
278
+ traceback.print_exc()
279
+ logger.error('Something went wrong...')
280
+ finally:
281
+ if db is not None:
282
+ try:
283
+ db.close()
284
+ except:
285
+ traceback.print_exc()
286
+ return exists
287
+
288
+
289
+ # so maybe xlsx file is a good way of storing data in the end... --> TODO maybe too...
290
+ def table_to_xlsx_with_sheets(db_path, output_xlsx_file):
291
+ """
292
+ Converts SQL tables in a database to an Excel file with separate sheets for each table.
293
+
294
+ Args:
295
+ db_path (str): The path to the database.
296
+ output_xlsx_file (str): The path to the output Excel file.
297
+
298
+ Returns:
299
+ None
300
+
301
+ Examples:
302
+ table_to_xlsx_with_sheets('database.db', 'output.xlsx')
303
+ """
304
+ # TODO maybe truncate tab name to 31 characters to avoid issues in Excel
305
+
306
+ # read all the tables and store them to the xlsx db
307
+ try:
308
+ db = TAsql(db_path)
309
+ tables = db.get_tables()
310
+
311
+ if db is not None:
312
+ try:
313
+ db.close()
314
+ except:
315
+ traceback.print_exc()
316
+
317
+ conn = sqlite3.connect(db_path)
318
+ with pd.ExcelWriter(output_xlsx_file) as writer:
319
+ for table in tables:
320
+ df = pandas.read_sql_query('SELECT * from '+table, conn)
321
+ df.to_excel(writer, sheet_name=table, index=False)
322
+
323
+ conn.close()
324
+ except:
325
+ traceback.print_exc()
326
+ logger.error('Something went wrong...')
327
+
328
+
329
+
330
+ class TAsql:
331
+ def __init__(self, filename_or_connection=None, add_useful_missing_SQL_commands=True):
332
+ """
333
+ Initializes a TAsql object.
334
+
335
+ Args:
336
+ filename_or_connection (str or sqlite3.Connection): The filename or connection to the SQLite database.
337
+ add_useful_missing_SQL_commands (bool): Whether to add useful missing SQL commands. Default is True.
338
+
339
+ Returns:
340
+ None
341
+ """
342
+ self.db_name = filename_or_connection
343
+ if isinstance(filename_or_connection, sqlite3.Connection):
344
+ self.con = filename_or_connection
345
+ self.db_name = None
346
+ logger.debug('Opened database from connection')
347
+ elif filename_or_connection is None:
348
+ # if no file name is specified, create an in-memory database
349
+ self.con = sqlite3.connect(":memory:")
350
+ self.db_name = ':memory:'
351
+ else:
352
+ if not os.path.exists(self.db_name):
353
+ parent_dir = os.path.dirname(self.db_name)
354
+ if not os.path.exists(parent_dir) and parent_dir != '':
355
+ # if parent folder does not exist, create it so that the db can be created too
356
+ os.makedirs(parent_dir, exist_ok=True)
357
+ self.con = sqlite3.connect(self.db_name)
358
+ logger.debug('Opened database: ' + str(self.db_name))
359
+
360
+ if add_useful_missing_SQL_commands:
361
+ self.add_useful_missing_SQL_commands()
362
+ self.cur = self.con.cursor()
363
+
364
+ def add_useful_missing_SQL_commands(self):
365
+ """
366
+ Adds useful missing SQL commands to the SQLite database connection.
367
+
368
+ Args:
369
+ None
370
+
371
+ Returns:
372
+ None
373
+ """
374
+ self.con.create_function("sin", 1, math.sin)
375
+ self.con.create_function("atan2", 2, math.atan2)
376
+ self.con.create_function("sqrt", 1, math.sqrt)
377
+
378
+ def drop_table(self, table_name):
379
+ """
380
+ Drops a table from the SQLite database.
381
+
382
+ Args:
383
+ table_name (str): The name of the table to drop.
384
+
385
+ Returns:
386
+ None
387
+ """
388
+ self.cur.execute('DROP TABLE IF EXISTS ' + table_name)
389
+
390
+ def create_and_append_table(self, table_name, datas, temporary=False):
391
+ """
392
+ Creates a new table in the SQLite database and appends data to it.
393
+
394
+ Args:
395
+ table_name (str): The name of the table to create.
396
+ datas (dict): A dictionary containing the column names as keys and the data as values.
397
+ temporary (bool, optional): Specifies whether the table is temporary. Defaults to False.
398
+
399
+ Returns:
400
+ None
401
+ """
402
+ # Create the table with the specified column names and types
403
+ self.create_table(table_name, list(datas.keys()), column_types=get_types_from_data(datas), temporary=temporary)
404
+
405
+ # Fill the table with data
406
+ self.fill_table(table_name, datas)
407
+
408
+
409
+ def create_table(self, table_name, columns, column_types=None, temporary=False):
410
+ """
411
+ Creates a new table in the SQLite database with the specified columns and column types.
412
+
413
+ Args:
414
+ table_name (str): The name of the table to create.
415
+ columns (list): A list of column names.
416
+ column_types (list, optional): A list of column types corresponding to the columns. Defaults to None.
417
+ temporary (bool, optional): Specifies whether the table is temporary. Defaults to False.
418
+
419
+ Returns:
420
+ None
421
+ """
422
+ # TODO if types are specified --> need add it
423
+
424
+ # Drop the table if it already exists
425
+ self.drop_table(table_name)
426
+
427
+ # Prepare the column content for the CREATE TABLE statement
428
+ col_content = columns
429
+ if isinstance(col_content, list):
430
+ if column_types is not None:
431
+ # Combine the column names and types
432
+ concat = list(zip(columns, column_types))
433
+ concat = [str(name) + ' ' + str(type) for name, type in concat]
434
+ col_content = _list_to_string(concat, add_quotes=False)
435
+ else:
436
+ col_content = _list_to_string(col_content, add_quotes=False)
437
+
438
+ # Create the table with the specified columns and column types
439
+ self.cur.execute('CREATE' + (' TEMPORARY' if temporary else '') + ' TABLE ' + table_name + ' (' + col_content + ')')
440
+
441
+ def fill_table(self, table_name, datas):
442
+ """
443
+ Fills the specified table in the SQLite database with the provided data.
444
+
445
+ Args:
446
+ table_name (str): The name of the table to fill.
447
+ datas (dict or list): The data to insert into the table. If a dictionary is provided, the values will be inserted as rows in the table.
448
+
449
+ Returns:
450
+ None
451
+ """
452
+ if isinstance(datas, dict):
453
+ # Convert the dictionary values to a list
454
+ datas = list(datas.values())
455
+
456
+ # Reorder the data into a more friendly format
457
+ datas = np.array(datas, dtype=object).T.tolist()
458
+
459
+ # TODO this is totally inefficient --> change the code some day to directly handle np.ndarray some day
460
+ if isinstance(datas, np.ndarray):
461
+ datas = datas.tolist()
462
+
463
+ # Iterate over the data and insert into the table
464
+ for data in datas:
465
+ # Convert the data list to a string
466
+ list_as_string = _list_to_string(data) # bug is here
467
+
468
+ # Generate the INSERT INTO command
469
+ COMMAND = "INSERT INTO " + table_name + " VALUES (" + list_as_string + ")"
470
+
471
+ # Check if the command has empty values and insert a row of NULL values
472
+ if COMMAND.endswith('()'):
473
+ print('empty values creating an empty row filled with NULL')
474
+ self.cur.execute("INSERT INTO " + table_name + " DEFAULT VALUES")
475
+ else:
476
+ self.cur.execute(COMMAND)
477
+
478
+ # Commit the changes to the database
479
+ self.con.commit()
480
+
481
+ def exists(self, table_name, attached_table=None):
482
+ """
483
+ Checks if a table exists in the SQLite database.
484
+
485
+ Args:
486
+ table_name (str): The name of the table to check.
487
+ attached_table (str, optional): The name of the attached table. Defaults to None.
488
+
489
+ Returns:
490
+ bool: True if the table exists, False otherwise.
491
+ """
492
+ if table_name is None:
493
+ return None
494
+
495
+ # Check if the table exists in the main database
496
+ if not '.' in table_name:
497
+ self.cur.execute("SELECT COUNT(name) FROM " + ('' if attached_table is None else str(
498
+ attached_table) + '.') + "sqlite_master WHERE type='table' AND name='" + table_name + "';")
499
+ else:
500
+ # Split the table name to detect temporary tables
501
+ master, table = table_name.split('.')
502
+ self.cur.execute("SELECT COUNT(name) FROM " + str(
503
+ master) + '.' + "sqlite_master WHERE type='table' AND name='" + table + "';")
504
+
505
+ # If the count is 1, then the table exists
506
+ if self.cur.fetchone()[0] == 1:
507
+ return True
508
+
509
+ return False
510
+
511
+ def save_query_to_csv_file(self, sql_command, output_file_name):
512
+ """
513
+ Executes an SQL query and saves the results to a CSV file.
514
+
515
+ Args:
516
+ sql_command (str): The SQL command to execute.
517
+ output_file_name (str): The name of the output CSV file.
518
+
519
+ Returns:
520
+ None
521
+ """
522
+ import pandas as pd
523
+
524
+ # Execute the SQL query and store the results in a pandas DataFrame
525
+ db_df = pd.read_sql_query(sql_command, self.con)
526
+
527
+ # Save the DataFrame to a CSV file
528
+ db_df.to_csv(output_file_name, index=False)
529
+
530
+ def get_tables(self, force_lower_case=False):
531
+ """
532
+ Retrieves the list of table names in the database.
533
+
534
+ Args:
535
+ force_lower_case (bool): If True, forces the table names to be returned in lowercase.
536
+
537
+ Returns:
538
+ list: The list of table names in the database.
539
+ """
540
+ try:
541
+ # Execute an SQL query to retrieve the table names from the database
542
+ self.cur.execute("SELECT name FROM sqlite_master WHERE type='table';")
543
+ query_result = self.cur.fetchall()
544
+ # Unpack the query result to extract the table names
545
+ names = self._unpack(query_result)
546
+ if force_lower_case:
547
+ # Convert table names to lowercase if specified
548
+ names = _to_lower(names)
549
+ return names
550
+ except:
551
+ # Something went wrong, assume no tables exist
552
+ return None
553
+
554
+ def close(self):
555
+ """
556
+ Closes the database connection.
557
+
558
+ This method commits any pending changes and closes the connection to the database.
559
+ """
560
+ try:
561
+ # Commit any pending changes before closing
562
+ self.con.commit()
563
+ except:
564
+ # Ignore any exceptions that occur during commit
565
+ pass
566
+
567
+ logger.debug('Closing database: ' + str(self.db_name))
568
+ # Close the database connection
569
+ self.con.close()
570
+
571
+ def get_table_header(self, tablename):
572
+ """
573
+ Retrieves the column names of a table.
574
+
575
+ Args:
576
+ tablename (str): The name of the table.
577
+
578
+ Returns:
579
+ list: The list of column names.
580
+ """
581
+ # Retrieve the column names using the get_table_column_names_and_types method
582
+ header = self.get_table_column_names_and_types(table_name=tablename, return_colnames_only=True)
583
+
584
+ # Return the column names
585
+ return header
586
+
587
+ def attach_table(self, dbName, nickName):
588
+ """
589
+ Attaches a new table to the current database.
590
+
591
+ Args:
592
+ dbName (str): The filename of the table to attach.
593
+ nickName (str): The nickname for the attached table.
594
+
595
+ Returns:
596
+ None
597
+ """
598
+ if dbName is not None:
599
+ # Replace backslashes with forward slashes in the filename
600
+ dbName = dbName.replace('\\\\', '/').replace('\\', '/')
601
+
602
+ # Execute the command to attach the table
603
+ self.execute_command("ATTACH DATABASE '" + dbName + "' AS '" + nickName + "'")
604
+
605
+ def detach_table(self, nickName):
606
+ """
607
+ Detaches a table from the current database.
608
+
609
+ Args:
610
+ nickName (str): The nickname of the table to detach.
611
+
612
+ Returns:
613
+ None
614
+ """
615
+ if nickName is not None:
616
+ # Execute the command to detach the table
617
+ self.execute_command("DETACH DATABASE '" + nickName + "'")
618
+
619
+ def execute_command(self, SQL_command, warn_on_error=True):
620
+ """
621
+ Executes an SQL command on the database.
622
+
623
+ Args:
624
+ SQL_command (str): The SQL command to execute.
625
+ warn_on_error (bool): If True, prints a warning message on error.
626
+
627
+ Returns:
628
+ None
629
+ """
630
+ if SQL_command is None:
631
+ return
632
+ try:
633
+ # Execute the SQL command
634
+ self.cur.execute(SQL_command)
635
+ self.cur.fetchall()
636
+ self.con.commit()
637
+ except:
638
+ if warn_on_error:
639
+ traceback.print_exc()
640
+ logger.error(
641
+ 'error executing the following command:\n"' + str(SQL_command) + '"' + '\ntable name:' + str(
642
+ self.db_name))
643
+
644
+ def EXCEPT(self, table_name, *columns_to_exclude): # concatTableName=False,
645
+ """
646
+ Generates a list of column names for a table excluding specified columns.
647
+
648
+ Args:
649
+ table_name (str): The name of the table.
650
+ *columns_to_exclude (str): Columns to exclude from the generated list.
651
+
652
+ Returns:
653
+ str: A comma-separated string of column names excluding the specified columns.
654
+ """
655
+ columns_to_exclude = [col.lower() for col in columns_to_exclude]
656
+ columns = self.get_table_column_names_and_types(table_name, return_colnames_only=True)
657
+ columns = [col.lower() for col in columns]
658
+ columns = [col for col in columns if col not in columns_to_exclude]
659
+
660
+ # Generate the comma-separated string of column names
661
+ return ', '.join(columns)
662
+
663
+ def run_SQL_command_and_get_results(self, SQL_command, return_header=False, warn_on_error=True):
664
+ """
665
+ Executes an SQL command and retrieves the results.
666
+
667
+ Args:
668
+ SQL_command (str): The SQL command to execute.
669
+ return_header (bool): If True, returns the header along with the results.
670
+ warn_on_error (bool): If True, prints a warning message on error.
671
+
672
+ Returns:
673
+ tuple: A tuple containing the results and optionally the header if return_header is True.
674
+ """
675
+ if SQL_command is None:
676
+ if return_header:
677
+ return None, None
678
+ return None
679
+
680
+ if SQL_command.count(';') > 1:
681
+ # Split the command and execute the last part
682
+ SQL_commands = SQL_command.strip().split(';')
683
+ SQL_commands = [sql_command for sql_command in SQL_commands if sql_command.strip() != '']
684
+
685
+ if len(SQL_commands) > 1:
686
+ last_command = SQL_commands[-1]
687
+ for command in SQL_commands[:-1]:
688
+ self.run_SQL_command_and_get_results(command, return_header=False, warn_on_error=warn_on_error)
689
+
690
+ # Execute the last command and retrieve the results
691
+ return self.run_SQL_command_and_get_results(last_command, return_header=return_header,
692
+ warn_on_error=warn_on_error)
693
+
694
+ try:
695
+ self.cur.execute(SQL_command)
696
+ query_result = self.cur.fetchall()
697
+
698
+ if return_header:
699
+ headers = self._unpack(self.cur.description)
700
+ return headers, query_result
701
+
702
+ return query_result
703
+ except:
704
+ # Command failed (e.g., table does not exist)
705
+ if warn_on_error:
706
+ traceback.print_exc()
707
+
708
+ if return_header:
709
+ return None, None
710
+ return None
711
+
712
+ def clean(self):
713
+ """
714
+ Cleans the database by removing unnecessary data.
715
+
716
+ This operation can lead to a strong size reduction in the database.
717
+
718
+ Returns:
719
+ None
720
+ """
721
+ logger.debug('Cleaning the database: ' + str(self.db_name))
722
+ self.run_SQL_command_and_get_results("VACUUM;")
723
+
724
+ def get_column(self, table_name, column_name, sort=None):
725
+ """
726
+ Retrieves the values of a specific column from a table.
727
+
728
+ Args:
729
+ table_name (str): The name of the table.
730
+ column_name (str): The name of the column.
731
+ sort (str): The sorting order of the results. Can be 'ASC' for ascending or 'DESC' for descending.
732
+
733
+ Returns:
734
+ list: The values of the specified column.
735
+ """
736
+ if not self.exists(table_name) or column_name is None:
737
+ return None
738
+ try:
739
+ results = self.run_SQL_command_and_get_results('SELECT "' + column_name + '" FROM "' + table_name + '"' + (
740
+ '' if sort is None else 'ORDER BY "' + column_name + '" ' + sort), return_header=False,
741
+ warn_on_error=False)
742
+ results = self._unpack(results)
743
+ return results
744
+ except:
745
+ return None
746
+
747
+ def _unpack(self, lst):
748
+ """
749
+ Unpacks a list of tuples by extracting the first element from each tuple.
750
+
751
+ Args:
752
+ lst (list): The list of tuples to unpack.
753
+
754
+ Returns:
755
+ list: The unpacked list containing the first element of each tuple.
756
+ """
757
+ out = [elem[0] for elem in lst]
758
+ return out
759
+
760
+ def get_table_column_names_and_types(self, table_name, return_colnames_only=False, attached_table=None):
761
+ """
762
+ Retrieves the column names and types of a table.
763
+
764
+ Args:
765
+ table_name (str): The name of the table.
766
+ return_colnames_only (bool): If True, returns only the column names. If False, returns a dictionary
767
+ mapping column names to their corresponding types.
768
+ attached_table (str): The name of the attached table, if applicable.
769
+
770
+ Returns:
771
+ list or dict: The column names and types of the table, depending on the value of return_colnames_only.
772
+ """
773
+ if not self.exists(table_name, attached_table=attached_table):
774
+ return None
775
+
776
+ if not '.' in table_name:
777
+ cols_and_types = self.run_SQL_command_and_get_results('PRAGMA ' + ('' if attached_table is None else str(
778
+ attached_table) + '.') + 'table_info("' + table_name + '");') # does not return attached ones --> need another code
779
+ else:
780
+ master, table = table_name.split('.')
781
+ cols_and_types = self.run_SQL_command_and_get_results(
782
+ 'PRAGMA ' + master + '.' + 'table_info("' + table + '");') # does not return attached ones --> need another code
783
+
784
+ if cols_and_types is None:
785
+ return None
786
+
787
+ cols_and_types = {col[1]: col[2] for col in cols_and_types}
788
+
789
+ if return_colnames_only:
790
+ return list(cols_and_types.keys())
791
+ else:
792
+ return cols_and_types
793
+
794
+ def get_min_max(self, table_name, column_name, freq=None, ignore_None_and_string=True, force_numeric=False):
795
+ """
796
+ Retrieves the minimum and maximum values of a column in a table.
797
+
798
+ Args:
799
+ table_name (str): The name of the table.
800
+ column_name (str): The name of the column.
801
+ freq (float or list or tuple): The frequency or range of frequencies at which to sample the data. If None,
802
+ the minimum and maximum values are returned. If a single float value is provided, it represents both the
803
+ lower and upper frequency bounds. If a list or tuple of two float values is provided, they represent
804
+ the lower and upper frequency bounds, respectively.
805
+ ignore_None_and_string (bool): If True, ignores None and string values when calculating the minimum and maximum.
806
+ force_numeric (bool): If True, converts values to numeric before calculating the minimum and maximum.
807
+
808
+ Returns:
809
+ tuple: A tuple containing the minimum and maximum values of the column.
810
+ """
811
+ if table_name is None or column_name is None:
812
+ return None, None
813
+
814
+ sorted_data = self.get_column(table_name, column_name, sort='ASC')
815
+ if sorted_data is None:
816
+ return None, None
817
+
818
+ # Call sort_col_numpy function with the appropriate parameters
819
+ return sort_col_numpy(sorted_data, freq=freq, ignore_None_and_string=ignore_None_and_string,
820
+ force_numeric=force_numeric, sort=False)
821
+
822
+ def getNbRows(self, tableName):
823
+ """
824
+ Counts the number of rows in a table.
825
+
826
+ Args:
827
+ tableName (str): The name of the table.
828
+
829
+ Returns:
830
+ int: The number of rows in the table.
831
+ """
832
+ if not self.exists(tableName):
833
+ return 0
834
+
835
+ SQLQuery = "SELECT COUNT(*) from '" + tableName + "';"
836
+ value = self.run_SQL_command_and_get_results(SQLQuery)
837
+
838
+ try:
839
+ return value[0][0]
840
+ except:
841
+ print('error')
842
+
843
+ return 0
844
+
845
+ def add_column(self, table_name, column_name, col_type=None, default_value='NULL'):
846
+ """
847
+ Adds a column to a table.
848
+
849
+ Args:
850
+ table_name (str): The name of the table.
851
+ column_name (str): The name of the column to be added.
852
+ col_type (str, optional): The data type of the column. Defaults to None.
853
+ default_value (str, optional): The default value for the column. Defaults to 'NULL'.
854
+ """
855
+ if not self.exists(table_name):
856
+ logger.error('Table ' + str(table_name) + ' does not exist.')
857
+ return
858
+
859
+ self.execute_command('ALTER TABLE ' + table_name + ' ADD COLUMN "' + column_name + '"' +
860
+ ('' if col_type is None else (' ' + str(col_type) + ' ')) +
861
+ ('' if default_value is None else ' DEFAULT ' + str(default_value)) + ';')
862
+
863
+ def remove_column(self, table_name, column_name):
864
+ """
865
+ Removes a column from a table.
866
+
867
+ Args:
868
+ table_name (str): The name of the table.
869
+ column_name (str): The name of the column to be removed.
870
+ """
871
+ if not self.exists(table_name):
872
+ logger.error('Table ' + str(table_name) + ' does not exist.')
873
+ return
874
+ try:
875
+ self.execute_command('ALTER TABLE ' + table_name + ' DROP COLUMN "' + column_name + '"')
876
+ except:
877
+ # If the column does not exist, there's nothing to drop, but it's not an error.
878
+ logger.warning(
879
+ 'Column does not exist and could not be dropped: ' + str(table_name) + ' ' + str(column_name))
880
+ pass
881
+
882
+ def print_query(self, SQL_command):
883
+ """
884
+ Prints the results of a SQL query in a formatted table.
885
+
886
+ Args:
887
+ SQL_command (str): The SQL query to execute and print the results.
888
+ """
889
+ # Retrieve the header and data from the SQL query
890
+ header, data = self.run_SQL_command_and_get_results(SQL_command, return_header=True)
891
+
892
+ # Create a PrettyTable instance
893
+ x = PrettyTable(header)
894
+
895
+ # Add each row of data to the PrettyTable
896
+ for row in data:
897
+ x.add_row(row)
898
+
899
+ # Print the PrettyTable
900
+ print(x)
901
+
902
+ def isTableEmpty(self, tableName):
903
+ """
904
+ Checks if a table is empty or does not exist.
905
+
906
+ Args:
907
+ tableName (str): The name of the table.
908
+
909
+ Returns:
910
+ bool: True if the table is empty or does not exist, False otherwise.
911
+ """
912
+ if not self.exists(tableName):
913
+ return True
914
+
915
+ if self.getNbRows(tableName) == 0:
916
+ return True
917
+
918
+ return False
919
+
920
+ def create_filtered_query(self, SQL_command, filtering_elements_dict=None):
921
+ """
922
+ Creates a filtered query based on the provided SQL command and filtering elements.
923
+
924
+ Args:
925
+ SQL_command (str): The original SQL command to filter.
926
+ filtering_elements_dict (dict): A dictionary where the keys are column names and the values are lists of elements
927
+ to filter.
928
+
929
+ Returns:
930
+ str: The filtered SQL query.
931
+
932
+ Examples:
933
+ filtering_elements_dict = {'area': [120, 130], 'local_ID': [33]}
934
+ create_filtered_query(SQL_command, filtering_elements_dict)
935
+ This will return: "SELECT * FROM (SQL_command) WHERE area IN (120, 130) AND local_ID IN (33)"
936
+ """
937
+ if filtering_elements_dict is None:
938
+ # Empty filter, return the default command
939
+ return SQL_command
940
+
941
+ if not filtering_elements_dict:
942
+ return SQL_command
943
+
944
+ initial_command = 'SELECT * FROM (' + SQL_command + ') WHERE '
945
+ extra_command = ''
946
+
947
+ for kkk, (k, v) in enumerate(filtering_elements_dict.items()):
948
+ filters = ', '.join(str(f) for f in v)
949
+ extra_command += ' ' + (' AND ' if kkk > 0 else '') + k + ' IN (%s)' % filters
950
+
951
+ query = initial_command + extra_command
952
+ return query
953
+
954
+ def filter_by_clone(self, table_name, cell_label):
955
+ """
956
+ Filters a database based on a clone table.
957
+
958
+ Args:
959
+ table_name (str): The name of the clone table in the database.
960
+ cell_label: The cell label image used to filter the database.
961
+
962
+ Returns:
963
+ list: The filtered local IDs.
964
+
965
+ Examples:
966
+ filter_by_clone('tracked_clone', cell_label)
967
+ This will return a list of filtered local IDs based on the provided clone table and cell label image.
968
+ """
969
+ coords = []
970
+ try:
971
+ coords = self.run_SQL_command_and_get_results('SELECT * FROM ' + table_name, return_header=False)
972
+ except:
973
+ traceback.print_exc()
974
+
975
+ local_ids = convert_coords_to_IDs(cell_label, coords, forbidden_colors=[0], return_image=False,
976
+ new_color_to_give_to_cells_if_return_image=None)
977
+ return local_ids
978
+ def any_to_numeric(values):
979
+ """
980
+ Converts values to numeric types if possible.
981
+
982
+ Args:
983
+ values (list): The list of values to convert.
984
+
985
+ Returns:
986
+ list: The converted values.
987
+
988
+ Examples:
989
+ values = ['123', '3.14', '456', '7.89']
990
+ converted_values = any_to_numeric(values)
991
+ This will convert the values in the list to their respective numeric types.
992
+ """
993
+ if values is None:
994
+ return None
995
+ for iii, v in enumerate(values):
996
+ if isinstance(v, str):
997
+ if '.' in v:
998
+ try:
999
+ values[iii] = float(v)
1000
+ except ValueError:
1001
+ values[iii] = None
1002
+ else:
1003
+ try:
1004
+ values[iii] = int(v)
1005
+ except ValueError:
1006
+ values[iii] = None
1007
+ return values
1008
+
1009
+ def get_numeric_value(v):
1010
+ """
1011
+ Converts a value to a numeric type if possible.
1012
+
1013
+ Args:
1014
+ v: The value to convert.
1015
+
1016
+ Returns:
1017
+ The converted numeric value if conversion is successful, None otherwise.
1018
+
1019
+ Examples:
1020
+ value = '3.14'
1021
+ converted_value = get_numeric_value(value)
1022
+ This will convert the value to a float type.
1023
+ """
1024
+ if isinstance(v, str):
1025
+ if '.' in v:
1026
+ try:
1027
+ return float(v)
1028
+ except ValueError:
1029
+ return None
1030
+ else:
1031
+ try:
1032
+ return int(v)
1033
+ except ValueError:
1034
+ return None
1035
+ return v
1036
+
1037
+
1038
+ import numpy as np
1039
+
1040
+ def sort_col_numpy(unsorted_data, freq=None, ignore_None_and_string=True, force_numeric=False, sort=True):
1041
+ """
1042
+ Sorts a column of data using NumPy and computes the minimum and maximum values.
1043
+
1044
+ Args:
1045
+ unsorted_data: The column of data to sort.
1046
+ freq: The frequency range to consider for computing the minimum and maximum values.
1047
+ ignore_None_and_string: Flag to ignore None and string values during sorting.
1048
+ force_numeric: Flag to force conversion of values to numeric types.
1049
+ sort: Flag to indicate whether to perform sorting.
1050
+
1051
+ Returns:
1052
+ The minimum and maximum values of the sorted column.
1053
+
1054
+ Examples:
1055
+ data = [3, 1, 2]
1056
+ min_val, max_val = sort_col_numpy(data)
1057
+ This will sort the data and compute the minimum and maximum values.
1058
+ """
1059
+ if unsorted_data is None:
1060
+ return None, None
1061
+
1062
+ sorted_data = unsorted_data
1063
+
1064
+ if force_numeric:
1065
+ sorted_data = any_to_numeric(sorted_data)
1066
+
1067
+ if ignore_None_and_string:
1068
+ sorted_data = [val for val in sorted_data if val is not None or isinstance(val, str)]
1069
+
1070
+ if sort:
1071
+ sorted_data = np.sort(unsorted_data)
1072
+
1073
+ length = len(sorted_data)
1074
+ min_val = sorted_data[0]
1075
+ max_val = sorted_data[-1]
1076
+
1077
+ if freq is None or freq == 0.:
1078
+ return min_val, max_val
1079
+ else:
1080
+ if isinstance(freq, list) or isinstance(freq, tuple):
1081
+ if len(freq) == 1:
1082
+ lower = freq[0]
1083
+ upper = freq[0]
1084
+ else:
1085
+ lower = freq[0]
1086
+ upper = freq[1]
1087
+ else:
1088
+ lower = freq
1089
+ upper = freq
1090
+
1091
+ if lower > 0.:
1092
+ idx = round(lower * length)
1093
+ try:
1094
+ min_val = sorted_data[idx]
1095
+ except:
1096
+ pass
1097
+
1098
+ if upper > 0.:
1099
+ idx = round(upper * length)
1100
+ try:
1101
+ max_val = sorted_data[-idx]
1102
+ except:
1103
+ pass
1104
+
1105
+ return min_val, max_val
1106
+
1107
+
1108
+
1109
+ def update_db_properties_using_image_properties(input_file, ta_path):
1110
+ """
1111
+ Update the database properties using the image properties.
1112
+
1113
+ Args:
1114
+ input_file (str): The path to the input image file.
1115
+ ta_path (str): The path to the database directory.
1116
+
1117
+ Raises:
1118
+ Exception: If an error occurs while reading properties from the image or writing them to the database.
1119
+ """
1120
+ try:
1121
+ tmp = Img(input_file) # Create an Img object from the input image file
1122
+ if has_metadata(tmp): # Check if the image has metadata
1123
+ db_path = os.path.join(ta_path, 'pyTA.db') # Construct the path to the database file
1124
+ db = TAsql(db_path) # Create a TAsql object with the database path
1125
+ try:
1126
+ # Initialize variables for storing the image properties
1127
+ voxel_size_x = None
1128
+ voxel_size_y = None
1129
+ voxel_size_z = None
1130
+ voxel_z_over_x_ratio = None
1131
+ time = None
1132
+ creation_time = None
1133
+
1134
+ # Extract the image properties from the metadata
1135
+ if 'vx' in tmp.metadata:
1136
+ voxel_size_x = tmp.metadata['vx']
1137
+ if 'vy' in tmp.metadata:
1138
+ voxel_size_y = tmp.metadata['vy']
1139
+ if 'vz' in tmp.metadata:
1140
+ voxel_size_z = tmp.metadata['vz']
1141
+ if 'AR' in tmp.metadata:
1142
+ voxel_z_over_x_ratio = tmp.metadata['AR']
1143
+ if 'time' in tmp.metadata:
1144
+ time = tmp.metadata['time']
1145
+ if 'creation_time' in tmp.metadata:
1146
+ creation_time = tmp.metadata['creation_time']
1147
+
1148
+ # Create a dictionary with the image properties
1149
+ neo_data = {'voxel_size_x': voxel_size_x,
1150
+ 'voxel_size_y': voxel_size_y,
1151
+ 'voxel_size_z': voxel_size_z,
1152
+ 'voxel_z_over_x_ratio': voxel_z_over_x_ratio,
1153
+ 'time': time,
1154
+ 'creation_time': creation_time}
1155
+
1156
+ if db.exists('properties'):
1157
+ # The 'properties' table exists in the database, update the existing data
1158
+ header, cols = db.run_SQL_command_and_get_results('SELECT * FROM properties', return_header=True)
1159
+ cols = cols[0]
1160
+ data = _to_dict(header, cols) # Convert the retrieved data to a dictionary
1161
+ for key in list(neo_data.keys()):
1162
+ if neo_data[key] is None:
1163
+ if key not in data:
1164
+ data[key] = neo_data[key]
1165
+ else:
1166
+ data[str(key)] = neo_data[key]
1167
+ else:
1168
+ # The 'properties' table does not exist, use the new data as it is
1169
+ data = neo_data
1170
+
1171
+ data = {k: [v] for k, v in data.items()} # Convert the data dictionary to a dictionary of lists
1172
+ db.create_and_append_table('properties', data) # Create or append the 'properties' table with the data
1173
+ except:
1174
+ traceback.print_exc()
1175
+ print('An error occurred while reading properties from the image or writing them to the database')
1176
+ finally:
1177
+ try:
1178
+ db.close() # Close the database connection
1179
+ except:
1180
+ pass
1181
+ del tmp # Delete the temporary Img object
1182
+ except:
1183
+ traceback.print_exc()
1184
+ print('Error could not save image properties to the TA database')
1185
+
1186
+ def _to_dict(header, col):
1187
+ """
1188
+ Convert the header and column data into a dictionary.
1189
+
1190
+ Args:
1191
+ header (list): The header containing the column names.
1192
+ col (list): The column data.
1193
+
1194
+ Returns:
1195
+ dict: A dictionary where the column names are the keys and the column data are the values.
1196
+ """
1197
+ dct = {}
1198
+ for iii, he in enumerate(header):
1199
+ dct[he] = col[iii] # Map the column name to its corresponding data value
1200
+ return dct
1201
+
1202
+
1203
+ def _list_to_string(lst, add_quotes=True):
1204
+ """
1205
+ Convert a list or dictionary to a string representation.
1206
+
1207
+ Args:
1208
+ lst (list or dict): The list or dictionary to be converted.
1209
+ add_quotes (bool, optional): Indicates whether quotes should be added around string values (default: True).
1210
+
1211
+ Returns:
1212
+ str: The string representation of the list or dictionary.
1213
+
1214
+ Examples:
1215
+ >>> lst = [1, 2, 3]
1216
+ >>> _list_to_string(lst)
1217
+ "'1', '2', '3'"
1218
+
1219
+ >>> lst = ['apple', 'banana', 'cherry']
1220
+ >>> _list_to_string(lst)
1221
+ "'apple', 'banana', 'cherry'"
1222
+
1223
+ >>> dct = {'Name': 'John', 'Age': 25, 'Country': 'USA'}
1224
+ >>> _list_to_string(dct)
1225
+ 'Name John, Age 25, Country USA'
1226
+ """
1227
+ if isinstance(lst, list):
1228
+ return ', '.join(
1229
+ map(
1230
+ lambda x: "'" + str(x) + "'" if x is not None and add_quotes else str(x) if x is not None and not add_quotes else 'Null',
1231
+ lst
1232
+ )
1233
+ )
1234
+ elif isinstance(lst, dict):
1235
+ return ', '.join(
1236
+ (str(x) + ' ' + str(y)) if y is not None else str(x) + ' ' + 'Null' for x, y in lst.items()
1237
+ )
1238
+ else:
1239
+ return list
1240
+
1241
+ # TODO get master db --> TODO --> required for global plots --> easy way of doing things in fact, I love it
1242
+
1243
+ # maybe allow unknown
1244
+ def get_type(value):
1245
+ """
1246
+ Determine the data type of a value.
1247
+
1248
+ Args:
1249
+ value (any): The value to determine the data type.
1250
+
1251
+ Returns:
1252
+ str: The data type of the value.
1253
+
1254
+ Examples:
1255
+ >>> get_type(42)
1256
+ 'INTEGER'
1257
+
1258
+ >>> get_type(3.14)
1259
+ 'FLOAT'
1260
+
1261
+ >>> get_type('Hello, world!')
1262
+ 'TEXT'
1263
+
1264
+ >>> get_type(True)
1265
+ 'BOOLEAN'
1266
+ """
1267
+ if isinstance(value, bool):
1268
+ return 'BOOLEAN'
1269
+ if isinstance(value, str):
1270
+ return 'TEXT'
1271
+ if isinstance(value, int):
1272
+ return 'INTEGER'
1273
+ if isinstance(value, float):
1274
+ return 'FLOAT'
1275
+ if isinstance(value, list):
1276
+ return 'TEXT'
1277
+ if isinstance(value, np.int64) or isinstance(value, np.uint64) \
1278
+ or isinstance(value, np.int32) or isinstance(value, np.uint32) \
1279
+ or isinstance(value, np.int8) or isinstance(value, np.uint8):
1280
+ return 'INTEGER'
1281
+ if isinstance(value, Img):
1282
+ return 'INTEGER'
1283
+ if isinstance(value, tuple) or isinstance(value, np.ndarray):
1284
+ return 'TEXT'
1285
+
1286
+ print('error type not supported:', type(value), value)
1287
+ return 'TEXT'
1288
+
1289
+
1290
+ def get_types_from_data(one_row_of_data):
1291
+ """
1292
+ Get the data types from a row of data.
1293
+
1294
+ Args:
1295
+ one_row_of_data (dict or list): A row of data represented as a dictionary or list.
1296
+
1297
+ Returns:
1298
+ list: A list of data types corresponding to the values in the row of data.
1299
+
1300
+ Examples:
1301
+ >>> get_types_from_data({'name': 'John', 'age': 30, 'height': 180.5})
1302
+ ['TEXT', 'INTEGER', 'FLOAT']
1303
+
1304
+ """
1305
+ types = []
1306
+ if isinstance(one_row_of_data, dict):
1307
+ for k, v in one_row_of_data.items():
1308
+ if v is None:
1309
+ types.append('TEXT')
1310
+ continue
1311
+
1312
+ if not isinstance(v, list):
1313
+ types.append(get_type(v))
1314
+ else:
1315
+ success = False
1316
+ for vv in v:
1317
+ if vv is not None:
1318
+ types.append(get_type(vv))
1319
+ success = True
1320
+ break
1321
+ if not success:
1322
+ types.append('TEXT')
1323
+ else:
1324
+ for data in one_row_of_data:
1325
+ types.append(get_type(data))
1326
+
1327
+ return types
1328
+
1329
+
1330
+ def _to_lower(lst):
1331
+ """
1332
+ Convert a list of elements to lowercase strings.
1333
+
1334
+ Args:
1335
+ lst (list): A list of elements.
1336
+
1337
+ Returns:
1338
+ list: A new list with elements converted to lowercase strings.
1339
+
1340
+ Examples:
1341
+ >>> _to_lower(['Apple', 'Banana', 'Orange'])
1342
+ ['apple', 'banana', 'orange']
1343
+
1344
+ >>> _to_lower(['Hello', 'WORLD'])
1345
+ ['hello', 'world']
1346
+ """
1347
+ return [str(elm).lower() for elm in lst]
1348
+
1349
+
1350
+ # NB this will fail when some command returns for some of the samples a null output --> think if there can be a workaround
1351
+ # MEGA TODO also I should always return the header and make sure it's identical to the previous one, because if it's not the case the aggregation is not possible --> then maybe set the row to None and later replace it with the appropriate nb of Null for example !!! --> think about that
1352
+ # TODO --> maybe also allow to save the master db --> pb is that it is never created the request is just made on the fly
1353
+ def combine_single_file_queries(lst, sql_command, table_names=None, db_name='pyTA.db', return_header=False, prepend_frame_nb=True, prepend_file_name=True, output_filename=None ): # , save_master_db=False
1354
+ """
1355
+ Combine the results of single file queries into a master data set.
1356
+
1357
+ Args:
1358
+ lst (list): List of file paths to query.
1359
+ sql_command (str): SQL command to execute for each file.
1360
+ table_names (str or list, optional): Names of tables to include in the query. Defaults to None.
1361
+ db_name (str, optional): Name of the database file. Defaults to 'pyTA.db'.
1362
+ return_header (bool, optional): Flag to indicate whether to return the header. Defaults to False.
1363
+ prepend_frame_nb (bool, optional): Flag to indicate whether to prepend the frame number to the data. Defaults to True.
1364
+ prepend_file_name (bool, optional): Flag to indicate whether to prepend the file name to the data. Defaults to True.
1365
+ output_filename (str, optional): Name of the output file to save the combined data. Defaults to None.
1366
+
1367
+ Returns:
1368
+ tuple or list: If `output_filename` is None and `return_header` is False, returns the combined data as a list.
1369
+ If `output_filename` is None and `return_header` is True, returns the header and combined data as a tuple.
1370
+ If `output_filename` is provided, saves the combined data to the specified file and returns None.
1371
+
1372
+ # Examples:
1373
+ # >>> lst = ['file1.db', 'file2.db', 'file3.db']
1374
+ # >>> sql_command = 'SELECT * FROM data'
1375
+ # >>> combine_single_file_queries(lst, sql_command, table_names='my_table', output_filename='combined_data.csv')
1376
+ #
1377
+ # >>> lst = ['file1.db', 'file2.db', 'file3.db']
1378
+ # >>> sql_command = 'SELECT * FROM data WHERE value > 10'
1379
+ # >>> header, data = combine_single_file_queries(lst, sql_command, return_header=True)
1380
+ # >>> print(header)
1381
+ # >>> print(data)
1382
+ """
1383
+
1384
+ if lst is not None and lst and isinstance(lst, list):
1385
+ # merged output
1386
+ master_data = []
1387
+ header = None
1388
+ for iii, file in enumerate(lst):
1389
+ # Parse the database file path
1390
+ db_path = smart_name_parser(file, db_name)
1391
+ # Create a TAsql instance for the database
1392
+ db = TAsql(db_path)
1393
+ try:
1394
+ final_command = sql_command
1395
+
1396
+ if table_names is not None:
1397
+ if isinstance(table_names, list):
1398
+ # Iterate over the table names and check if they exist in the database
1399
+ for table_name in table_names:
1400
+ if db.exists(table_name):
1401
+ final_command += table_name
1402
+ break
1403
+ else:
1404
+ final_command += table_names
1405
+
1406
+ # Execute the SQL command and get the results
1407
+ out = db.run_SQL_command_and_get_results(final_command, return_header=iii == 0)
1408
+ if isinstance(out, tuple):
1409
+ header, data = out
1410
+ if prepend_file_name:
1411
+ header = prepend_to_content(header, 'filename')
1412
+ if prepend_frame_nb:
1413
+ header = prepend_to_content(header, 'frame_nb')
1414
+ else:
1415
+ data = out
1416
+ if prepend_file_name:
1417
+ data = prepend_to_content(data, file)
1418
+ if prepend_frame_nb:
1419
+ data = prepend_to_content(data, iii)
1420
+
1421
+ # Append the data to the master data list
1422
+ master_data.extend(data)
1423
+ except:
1424
+ traceback.print_exc()
1425
+ finally:
1426
+ # if save_master_db:
1427
+ # if isinstance(save_master_db, bool):
1428
+ # save_master_db = smart_name_parser(output_filename, 'full_no_ext')+'_master.db'
1429
+ #
1430
+ # Close the database connection
1431
+ db.close()
1432
+
1433
+ if not master_data:
1434
+ master_data = None
1435
+ if output_filename is not None:
1436
+ # Save the combined data to the output file
1437
+ save_data_to_csv(output_filename, header, master_data)
1438
+ else:
1439
+ if return_header:
1440
+ return header, master_data
1441
+ return master_data
1442
+ else:
1443
+ logger.error('No input list -> nothing to do...')
1444
+ if return_header:
1445
+ return None, None
1446
+ return None
1447
+
1448
+
1449
+ # TODO KEEP MEGA URGENT do force_track_cells_db_update for bonds and vertices when the tracking will be implemented.
1450
+ # TODO --> find a way to handle clones here
1451
+ def createMasterDB(lst, outputName=None, progress_callback=None, database_name=None, force_track_cells_db_update=False, db_name='pyTA.db'):
1452
+ """
1453
+ Creates a master database by combining data from multiple database files.
1454
+
1455
+ Args:
1456
+ lst (list): List of input filenames or database file paths.
1457
+ outputName (str, optional): Name of the output database file. If provided, the existing file will be removed
1458
+ before creating a new one. Defaults to None.
1459
+ progress_callback (function, optional): Callback function to track the progress of the database creation.
1460
+ Defaults to None.
1461
+ database_name (str, optional): Name of a specific database to include. Only the tables from this database will
1462
+ be added to the master database. Defaults to None.
1463
+ force_track_cells_db_update (bool, optional): If True, forces an update to the 'cell_tracks' table in the
1464
+ database. Defaults to False.
1465
+ db_name (str, optional): Name of the database. Defaults to 'pyTA.db'.
1466
+
1467
+ Returns:
1468
+ TAsql: The master database object.
1469
+
1470
+ Examples:
1471
+ lst = ['db1.db', 'db2.db', 'db3.db']
1472
+ createMasterDB(lst, outputName='master.db')
1473
+
1474
+ This example will create a master database named 'master.db' by combining the tables from 'db1.db', 'db2.db',
1475
+ and 'db3.db'. The existing 'master.db' file, if any, will be removed before creating the new database.
1476
+ """
1477
+ masterDB = None
1478
+ try:
1479
+ frame_nb = "frame_nb"
1480
+ fileName = "filename"
1481
+
1482
+ # Create or remove the output file if specified
1483
+ if outputName is not None:
1484
+ try:
1485
+ os.remove(outputName)
1486
+ except OSError:
1487
+ pass
1488
+
1489
+ # Generate a list of database filenames
1490
+ database_list = smart_TA_list(lst, db_name)
1491
+
1492
+ # Create the masterDB object
1493
+ masterDB = TAsql(filename_or_connection=outputName, add_useful_missing_SQL_commands=False)
1494
+
1495
+ # Iterate over each database file
1496
+ if database_list is not None:
1497
+ for l, db_l in enumerate(database_list):
1498
+ try:
1499
+ # Check if the process should be stopped
1500
+ if early_stop.stop:
1501
+ return
1502
+
1503
+ # Update progress if a callback function is provided
1504
+ if progress_callback is not None:
1505
+ progress_callback.emit(int((l / len(database_list)) * 100))
1506
+ else:
1507
+ logger.info(str((l / len(database_list)) * 100) + '%')
1508
+ except:
1509
+ pass
1510
+
1511
+ # Open the database file
1512
+ dbHandler = None
1513
+ try:
1514
+ dbHandler = TAsql(filename_or_connection=db_l)
1515
+
1516
+ # Get all tables in the database
1517
+ tables = dbHandler.get_tables()
1518
+
1519
+ if force_track_cells_db_update:
1520
+ force_update = False
1521
+
1522
+ # Check if 'cell_tracks' table is missing or empty
1523
+ if 'cell_tracks' not in tables or dbHandler.isTableEmpty('cell_tracks'):
1524
+ force_update = True
1525
+
1526
+ if not force_update:
1527
+ try:
1528
+ # Check if 'track_id' column is empty
1529
+ data = dbHandler.run_SQL_command_and_get_results('SELECT track_id FROM cell_tracks LIMIT 1')
1530
+ if data == None or data[0][0] == None:
1531
+ force_update = True
1532
+ except:
1533
+ force_update = True
1534
+
1535
+ if force_update:
1536
+ # Drop 'cell_tracks' table and create a new one
1537
+ dbHandler.drop_table('cell_tracks')
1538
+ dbHandler.execute_command('CREATE TABLE cell_tracks AS SELECT local_id as local_id, CAST(NULL AS INTEGER) AS track_id FROM cells_2D')
1539
+
1540
+ masterDBTables = masterDB.get_tables()
1541
+
1542
+ # Iterate over each table in the database
1543
+ for string in tables:
1544
+ if database_name is not None:
1545
+ if database_name.lower() != string.lower():
1546
+ continue
1547
+ if string not in masterDBTables: # (!masterDBTables.contains(string)) {
1548
+ tableHeaderAndType = dbHandler.get_table_column_names_and_types(string)
1549
+ extra = {}
1550
+ extra[frame_nb] = 'INTEGER'
1551
+ extra[fileName] = 'TEXT'
1552
+
1553
+ for string1 in tableHeaderAndType.keys():
1554
+ extra[string1] = tableHeaderAndType[string1]
1555
+ masterDB.create_table(string, list(extra.keys()), list(extra.values()))
1556
+
1557
+ masterDB.attach_table(db_l, 'tmp') # --> so it is needed then # do I use that ????
1558
+ dbHandler.close()
1559
+ # in case there is a mismatch in the columns then do stuff to fix it
1560
+
1561
+ for string in tables:
1562
+ if database_name is not None:
1563
+ if database_name.lower() != string.lower():
1564
+ continue
1565
+ DB_to_read = 'tmp.' + str(string)
1566
+ cols = masterDB.get_table_column_names_and_types(DB_to_read)
1567
+ cols_master = masterDB.get_table_column_names_and_types(string)
1568
+
1569
+ fixed_cols = set(_to_lower(cols.keys()))
1570
+ fixed_cols.add(frame_nb)
1571
+ fixed_cols.add(fileName)
1572
+
1573
+ fixed_master = set(_to_lower(cols_master.keys()))
1574
+
1575
+ # make it case insensitive
1576
+ cols_master = {k.lower(): v for k, v in
1577
+ cols_master.items()} # TODO maybe replace by a case insensitive dict
1578
+ cols = {k.lower(): v for k, v in cols.items()}
1579
+
1580
+ if not fixed_cols == fixed_master:
1581
+ present_in_master_but_missing_in_cur = fixed_master - fixed_cols
1582
+ present_in_cur_but_missing_in_master = fixed_cols - fixed_master
1583
+
1584
+ if present_in_master_but_missing_in_cur:
1585
+ # Create a temporary table for adding missing columns
1586
+ masterDB.drop_table('pytaTMP')
1587
+ masterDB.execute_command('CREATE TABLE pytaTMP AS SELECT * from ' + str(DB_to_read))
1588
+
1589
+ for col in present_in_master_but_missing_in_cur:
1590
+ # Add missing columns to the temporary table
1591
+ masterDB.add_column('pytaTMP', col, col_type=cols_master[col])
1592
+
1593
+ DB_to_read = 'pytaTMP'
1594
+
1595
+ for col in present_in_cur_but_missing_in_master:
1596
+ # Add missing columns to the masterDB table
1597
+ masterDB.add_column(string, col, col_type=cols[col])
1598
+
1599
+ name = smart_name_parser(lst[l], ordered_output=['short'])[0]
1600
+
1601
+ # Insert data from the database table into the masterDB table
1602
+ masterDB.execute_command(
1603
+ "INSERT INTO '" + str(string) + "' SELECT " + str(l) + " AS '" + str(
1604
+ frame_nb) + "', '" + str(
1605
+ name) + "' AS '" + str(fileName) + "', * FROM " + str(DB_to_read))
1606
+
1607
+ # Drop the temporary table
1608
+ masterDB.drop_table('pytaTMP')
1609
+
1610
+ except:
1611
+ traceback.print_exc()
1612
+ finally:
1613
+ if dbHandler is not None:
1614
+ # Detach the database table and close the connection
1615
+ masterDB.detach_table('tmp')
1616
+ dbHandler.close()
1617
+
1618
+ except:
1619
+ traceback.print_exc()
1620
+
1621
+ finally:
1622
+ # print(outputName)
1623
+
1624
+ # Clean up and close the masterDB if an outputName is provided
1625
+ if masterDB is not None and outputName is not None:
1626
+ masterDB.clean()
1627
+ masterDB.close()
1628
+ else:
1629
+ return masterDB
1630
+
1631
+
1632
+ def set_property(db_file, neo_data):
1633
+ """
1634
+ Sets properties in a database table using data from Neo.
1635
+
1636
+ Args:
1637
+ db_file (str): The path to the database file.
1638
+ neo_data (dict): The dictionary containing the Neo data.
1639
+
1640
+ Returns:
1641
+ None
1642
+
1643
+ Raises:
1644
+ Exception: If an error occurs while inserting new properties.
1645
+
1646
+ # Examples:
1647
+ # >>> db_file = 'data.db'
1648
+ # >>> neo_data = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}
1649
+ # >>> set_property(db_file, neo_data)
1650
+ """
1651
+ if not neo_data:
1652
+ return
1653
+
1654
+ try:
1655
+ db = TAsql(db_file)
1656
+
1657
+ try:
1658
+ if db.exists('properties'):
1659
+ header, cols = db.run_SQL_command_and_get_results('SELECT * FROM properties', return_header=True)
1660
+ cols = cols[0]
1661
+ data = _to_dict(header, cols)
1662
+ data.update(neo_data)
1663
+ else:
1664
+ data = neo_data
1665
+
1666
+ data = {k: [v] for k, v in data.items()}
1667
+ db.create_and_append_table('properties', data)
1668
+ except:
1669
+ traceback.print_exc()
1670
+ print('An error occurred while reading properties from the image or writing them to the database')
1671
+ finally:
1672
+ try:
1673
+ db.close()
1674
+ except:
1675
+ pass
1676
+ except:
1677
+ traceback.print_exc()
1678
+ print('Error could not insert new properties')
1679
+
1680
+
1681
+ # returns the desired property from a TA db for the current file or None if not found
1682
+ def get_property(db_file, property_name):
1683
+ """
1684
+ Retrieves a property value from a database table.
1685
+
1686
+ Args:
1687
+ db_file (str): The path to the database file.
1688
+ property_name (str): The name of the property to retrieve.
1689
+
1690
+ Returns:
1691
+ str or None: The value of the property, or None if it does not exist or an error occurs.
1692
+
1693
+ # Examples:
1694
+ # >>> db_file = 'data.db'
1695
+ # >>> property_name = 'key1'
1696
+ # >>> result = get_property(db_file, property_name)
1697
+ # >>> print(result)
1698
+ # 'value1'
1699
+ """
1700
+ val = None
1701
+ db = TAsql(db_file)
1702
+
1703
+ try:
1704
+ val = db.run_SQL_command_and_get_results('SELECT ' + property_name + ' from properties')[0][0]
1705
+
1706
+ if isinstance(val, str) and (val.lower() == 'none' or val.lower() == 'null' or val.strip() == ''):
1707
+ val = None
1708
+ except:
1709
+ # If anything goes wrong or the table or column does not exist, return None
1710
+ pass
1711
+
1712
+ db.close()
1713
+ return val
1714
+
1715
+
1716
+ def get_properties_master_db(lst):
1717
+ """
1718
+ Retrieves properties from multiple databases and creates a master database.
1719
+
1720
+ Args:
1721
+ lst (list): The list of database names.
1722
+
1723
+ Returns:
1724
+ TAsql: The master database containing the properties.
1725
+
1726
+ # Examples:
1727
+ # >>> lst = ['db1', 'db2', 'db3']
1728
+ # >>> result = get_properties_master_db(lst)
1729
+ # >>> print(result)
1730
+ # <TAsql object at 0x...>
1731
+ """
1732
+ database_list = smart_TA_list(lst, 'pyTA.db')
1733
+
1734
+ if database_list is not None:
1735
+ for db_file in database_list:
1736
+ db = TAsql(db_file)
1737
+
1738
+ if not 'properties' in db.get_tables():
1739
+ db.create_table('properties', ['voxel_z_over_x_ratio', 'time'], ['float', 'float'])
1740
+ db.execute_command("INSERT INTO properties ('voxel_z_over_x_ratio', 'time') "
1741
+ "SELECT NULL, NULL "
1742
+ "WHERE NOT EXISTS (SELECT * FROM properties)"
1743
+ )
1744
+ else:
1745
+ if db.isTableEmpty('properties'):
1746
+ db.execute_command("INSERT INTO properties ('voxel_z_over_x_ratio', 'time') "
1747
+ "SELECT NULL, NULL "
1748
+ "WHERE NOT EXISTS (SELECT * FROM properties)"
1749
+ )
1750
+
1751
+ db.close()
1752
+
1753
+ database = createMasterDB(lst, database_name='properties')
1754
+
1755
+ return database
1756
+
1757
+ def reinject_properties_to_TA_files(lst, master_db, indices_to_update=None):
1758
+ """
1759
+ Reinjects properties from the master database to individual TA files.
1760
+
1761
+ Args:
1762
+ lst (list): The list of TA file names.
1763
+ master_db (TAsql): The master database containing the properties.
1764
+ indices_to_update (list or None): The indices of the TA files to update. If None, update all files.
1765
+
1766
+ Returns:
1767
+ None
1768
+
1769
+ # Examples:
1770
+ # >>> lst = ['db1', 'db2', 'db3']
1771
+ # >>> master_db = TAsql('master.db')
1772
+ # >>> indices_to_update = [0, 2]
1773
+ # >>> reinject_properties_to_TA_files(lst, master_db, indices_to_update)
1774
+ """
1775
+ database_list = smart_TA_list(lst, 'pyTA.db')
1776
+
1777
+ for iii, db_file in enumerate(database_list):
1778
+ if indices_to_update is not None:
1779
+ if iii not in indices_to_update:
1780
+ continue
1781
+
1782
+ master_db.attach_table(db_file, 'tmp')
1783
+
1784
+ master_db.execute_command('DROP TABLE IF EXISTS tmp.properties')
1785
+
1786
+ master_db.execute_command('CREATE TABLE tmp.properties AS SELECT ' +
1787
+ master_db.EXCEPT('properties', *['frame_nb', 'filename']) +
1788
+ ' FROM properties WHERE frame_nb =' + str(iii))
1789
+
1790
+ master_db.execute_command('DETACH DATABASE tmp')
1791
+
1792
+ # TODO also I would need handle polarity smarty but keep it channels --> TODO --> treat all of them as a single piece of data
1793
+
1794
+ def populate_table_content(db_name, prepend='#', filtered_out_columns=['x', 'y', 'first_pixel', 'local_ID', 'pixel_within_cell', 'centroid', 'vx', 'cell_id', 'pixel_within_', 'perimeter_pixel_count', 'bond_cut_off', 'vertices', 'bonds', 'pixel_count']):
1795
+ """
1796
+ Populates the content of a table in a database.
1797
+
1798
+ Args:
1799
+ db_name (str): The name of the database file.
1800
+ prepend (str): The string to prepend to each table and column name. Default is '#'.
1801
+ filtered_out_columns (list): The list of column names to filter out. Default is a list of column names.
1802
+
1803
+ Returns:
1804
+ list: The list of table and column names.
1805
+
1806
+ # Examples:
1807
+ # >>> db_name = 'data.db'
1808
+ # >>> prepend = '#'
1809
+ # >>> filtered_out_columns = ['x', 'y', 'first_pixel', 'local_ID']
1810
+ # >>> result = populate_table_content(db_name, prepend, filtered_out_columns)
1811
+ # >>> print(result)
1812
+ # ['#table1.column1', '#table1.column2', '#table2.column1', '#table2.column2']
1813
+ """
1814
+ if not os.path.isfile(db_name):
1815
+ return None
1816
+
1817
+ table_content = []
1818
+ db = None
1819
+
1820
+ try:
1821
+ db = TAsql(db_name)
1822
+ tables = db.get_tables()
1823
+
1824
+ for table in tables:
1825
+ columns = db.get_table_column_names_and_types(table, return_colnames_only=True)
1826
+ cols_to_remove = []
1827
+
1828
+ if filtered_out_columns is not None:
1829
+ for filter in filtered_out_columns:
1830
+ for col in columns:
1831
+ if col.startswith(filter) or col == filter:
1832
+ cols_to_remove.append(col)
1833
+
1834
+ columns = [col for col in columns if col not in cols_to_remove]
1835
+
1836
+ for col in columns:
1837
+ table_content.append(('' if prepend is None else str(prepend)) + str(table) + '.' + str(col))
1838
+ except:
1839
+ traceback.print_exc()
1840
+ logger.error('Error populating the database')
1841
+ finally:
1842
+ if db is not None:
1843
+ db.close()
1844
+
1845
+ return table_content
1846
+
1847
+
1848
+ # do a sql class to store all TA data --> see what is the best way to add all
1849
+ # maybe do different classes
1850
+ # maybe a map with each column title and a corresponding array of values would be a good idea in fact to get started
1851
+ # or a row per row stuff and a header is also interesting --> really give it a try
1852
+ # maybe also a typing of the table is a good idea
1853
+ if __name__ == '__main__':
1854
+ import sys
1855
+
1856
+ if True:
1857
+ columns = ['id', 'fruit']
1858
+ data = np.array( [['1', 'apple'], ['2', 'banana'], ['3', 'cherry']])
1859
+ column_types = ['INTEGER', 'TEXT']
1860
+ create_table_and_append_data("mydb.db", "fruits", columns, data, column_types=column_types, temporary=False)
1861
+ sys.exit(0)
1862
+
1863
+
1864
+ if True:
1865
+ table_to_xlsx_with_sheets('/E/Sample_images/FISH/Sample_transcription_dot_detection/manue_manually_segmented_images/training_set/tests/full_real_analysis_tst1/Sarah/kniR-405_knrlR-488_kniD-565_kniD-633/Image 23_Stitch/FISH.db', '/E/Sample_images/FISH/Sample_transcription_dot_detection/manue_manually_segmented_images/training_set/tests/full_real_analysis_tst1/Sarah/kniR-405_knrlR-488_kniD-565_kniD-633/Image 23_Stitch/FISH.xlsx')
1866
+
1867
+
1868
+ sys.exit(0)
1869
+
1870
+ if True:
1871
+
1872
+ # print(os.path.exists('/E/Sample_images/FISH/Sample_transcription_dot_detection/manue_manually_segmented_images/training_set/tests/full_real_analysis_tst1/Benoit R1 R6/R1 565 R6 633 f W5 NS3/FISHcopie.db'))
1873
+ remove_dupes_from_table_and_overwrite_table('/E/Sample_images/FISH/Sample_transcription_dot_detection/manue_manually_segmented_images/training_set/tests/full_real_analysis_tst1/Benoit R1 R6/R1 565 R6 633 f W5 NS3/FISH (copie).db', 'human_curated_distances_3D')
1874
+ remove_dupes_from_table_and_overwrite_table('/E/Sample_images/FISH/Sample_transcription_dot_detection/manue_manually_segmented_images/training_set/tests/full_real_analysis_tst1/Benoit R1 R6/R1 565 R6 633 f W5 NS3/FISH (copie).db', 'human_curated_distances_3D_chromatic_aberrations_corrected')
1875
+
1876
+
1877
+ sys.exit(0)
1878
+
1879
+ if False:
1880
+ print(table_exists_in_db('/E/Sample_images/FISH/Sample_transcription_dot_detection/manue_manually_segmented_images/training_set/tests/full_real_analysis_tst1/tmp/R1 565 R6 633 f S1/FISH.db', 'human_curated_distances_3D'))
1881
+
1882
+ print(table_exists_in_db('/E/Sample_images/FISH/Sample_transcription_dot_detection/manue_manually_segmented_images/training_set/tests/full_real_analysis_tst1/tmp/R1 565 R6 633 f S1/FISH.db', 'breaking_bad'))
1883
+
1884
+ sys.exit(0)
1885
+
1886
+ # --> could create the master db and then even further filter it
1887
+ # can I also do ins by sets --> think about it
1888
+
1889
+ # NB in is same as
1890
+ #SELECT * FROM employees WHERE employee_id = 1 OR employee_id = 2 OR employee_id = 3 OR employee_id = 4;
1891
+ # SELECT * FROM employees WHERE first_name NOT IN ('Sarah', 'Jessica'); # maybe useful too
1892
+
1893
+ if False:
1894
+ db = TAsql('/E/Sample_images/sample_images_PA/mini_different_nb_of_channels/focused_Series012/pyTA.db')
1895
+ # print(db.create_filtered_query('SELECT * from cells_2D', filtering_elements=[1,2,120],filter_name='local_ID'))
1896
+ print(db.create_filtered_query('SELECT * from cells_2D', filtering_elements_dict={'local_ID':[1,2,120]}))
1897
+ print(db.create_filtered_query('SELECT * from cells_2D', filtering_elements_dict={'local_ID':[1,2,120], 'cytoplasmic_area':[802]}))
1898
+ db.close()
1899
+
1900
+ sys.exit(0)
1901
+
1902
+ if False:
1903
+ new_properties = {'reg_x':16, 'reg_y':-32}
1904
+ set_property(
1905
+ '/E/Sample_images/sample_images_PA/mini_different_nb_of_channels/focused_Series012/pyTA.db',
1906
+ new_properties) # ça marche!!!
1907
+ print(get_property(
1908
+ '/E/Sample_images/sample_images_PA/mini_different_nb_of_channels/focused_Series012/pyTA.db',
1909
+ 'reg_x') + 1) # ça marche!!!
1910
+ sys.exit(0)
1911
+
1912
+ if False:
1913
+ lst = {'toto':'tutu', 'tata':None}
1914
+ print(', '.join((str(x) + ' ' + str(y)) if y is not None else str(x) + ' ' + 'Null' for x, y in lst.items()))
1915
+ import sys
1916
+ sys.exit(0)
1917
+
1918
+ lst = [0, None, 123]
1919
+ print(_list_to_string(lst))
1920
+
1921
+
1922
+ print(', '.join(map(str, lst)))
1923
+ print(', '.join(map(lambda x: str(x) if x is not None else 'Null', lst)))
1924
+ print("'" + '\', \''.join(map(lambda x: str(x) if x is not None else 'Null', lst)) + "'")
1925
+ add_quotes = True
1926
+ print(', '.join(map(lambda x: "'"+str(x)+"'" if x is not None and add_quotes else str(x) if x is not None and not add_quotes else 'Null', lst)))
1927
+
1928
+
1929
+ if False:
1930
+ # use these properties to compute the real 3D area values --> maybe also store pc data in this
1931
+
1932
+ print(get_property('/E/Sample_images/sample_images_PA/mini_different_nb_of_channels/focused_Series012/pyTA.db', 'time')+1) # ça marche!!!
1933
+ print(get_property('/E/Sample_images/sample_images_PA/mini_different_nb_of_channels/focused_Series012/pyTA.db', 'voxel_z_over_x_ratio'))
1934
+ import sys
1935
+ sys.exit(0)
1936
+
1937
+
1938
+ if True:
1939
+ # do a real mount maybe ???
1940
+
1941
+ # all seems ok and in many aspects simpler than the java equivalent...
1942
+ # sql_file = '/E/Sample_images/sample_images_PA/mini/test.db'
1943
+ # sql_file = '/run/user/1000/gvfs/smb-share:server=teamdfs2,share=teamprudhomme/EqpPrudHomme2/To be analyzed/221123 R1y R6y/R1 565 R6 633 f S3/test.db' # test for sqlite on samba --> there is a bug on a samba drive --> how can I fix that
1944
+ sql_file = '/media/eqpPrudhomme/EqpPrudHomme2/To be analyzed/test.db' # FINALLY GOT IT TO WORK --> use the nobrl option in mount CIFS # test for sqlite on samba --> there is a bug on a samba drive --> how can I fix that # TO GET IT TO WORK I NEED THE 'NOBRL' command !!!
1945
+ table_name = 'test'
1946
+ table_columns = ['aaa', 'bbb', 'ccc', 'ddd', 'eee']
1947
+ table_cols_and_type = {'zzz': None, 'aaa': 'DOUBLE', 'bbb': 'INT',
1948
+ 'ccc': 'TEXT'} # see all the types that exist and stick to that!!!
1949
+
1950
+ one_row = [None, 10, 123., True, 'toto']
1951
+
1952
+ print(_list_to_string(['10', '10.0', '20', 'TEXT1']))
1953
+
1954
+ # magic table
1955
+ table_magic = {'zzz': [10, 20], 'aaa': [10., 22.], 'bbb': [20, 30],
1956
+ 'ccc': ['TEXT1', 'TEXT2']}
1957
+
1958
+ print(_list_to_string(table_columns), table_columns)
1959
+ print(_list_to_string(table_cols_and_type), table_cols_and_type)
1960
+
1961
+ print('line below will generate an error!')
1962
+ print(get_types_from_data(one_row))
1963
+
1964
+ # now really create a table and then fill it --> TODO
1965
+
1966
+ # test of all
1967
+ db = TAsql(sql_file) # maybe if file is none --> store in mem ???
1968
+ db.create_and_append_table(table_name=table_name, datas=table_magic)
1969
+ # create and append table
1970
+
1971
+ data = db.run_SQL_command_and_get_results('SELECT * FROM test WHERE ccc == "TEXT2"')
1972
+ print(data) # --> just gives me the data row by row --> probably pandas offers me more flexibility!!!
1973
+
1974
+ headers = db.get_table_header('test2')
1975
+ print('headers', headers)
1976
+
1977
+ headers = db.get_table_header('test')
1978
+ print('headers', headers)
1979
+
1980
+ print(db.exists('test 2312'))
1981
+ print(db.exists('test'))
1982
+
1983
+ print('list of tables', db.get_tables())
1984
+ # [('table1',), ('table2',), ('table3',)]
1985
+
1986
+ # sqlite3.Warning: You can only execute one statement at a time. -> see how I can concatenate the stuff
1987
+ # maybe just return the last data --> does make sense in fact
1988
+ # --> split the command and execute it
1989
+ print('test multi', db.run_SQL_command_and_get_results('SELECT * FROM test;'
1990
+ 'SELECT "aaa" FROM test;')) # bug
1991
+
1992
+ print('get col', db.get_column('test', 'aaa'), type(db.get_column('test', 'aaa')[
1993
+ 0])) # --> the type is correct --> so in theory no need for casting but maybe if wrong type then it is needed
1994
+
1995
+ # can use the stuff
1996
+ # can use the table headers TODO plots --> in fact that would be easy TODO I think
1997
+ # --> can easily add anything as a graph if I need it
1998
+ # add an edit for the images at the very end in the preview --> see how I can do that --> would be good if cell divisions and death could be edited there --> but need keep the cell relationship --> in fact easy just delete the table
1999
+
2000
+ # print type
2001
+
2002
+ # can do automatic plots for all
2003
+ # and maybe also offer color coding for all # --> either local or global --> in fact all is easy todo with my tool now!!!
2004
+
2005
+ # for some columns the plots should be specific for others
2006
+
2007
+ db.add_column('test', 'uuu', default_value=None)
2008
+ print(db.get_table_column_names_and_types('test'))
2009
+
2010
+ db.get_min_max('test', 'aaa')
2011
+
2012
+ db.clean()
2013
+
2014
+ db.close()
2015
+
2016
+ # try create a table
2017
+ # maybe also need typing of the data --> try
2018
+
2019
+ import sys
2020
+ sys.exit(0)
2021
+
2022
+ if False:
2023
+ values = ['10', 20, 30, None, '10.2', '0.1', '0', 5]
2024
+ convert_digit_to_number = True
2025
+ if convert_digit_to_number:
2026
+ non_str = [val for val in values if not isinstance(val, str)]
2027
+ st = [val for val in values if isinstance(val, str)]
2028
+ st = [float(val) for val in st if val.isdigit()] # ok but it does change the order --> should I care
2029
+
2030
+ # could convert to None all the data that cannot be processed!!!
2031
+
2032
+ print(values)
2033
+ print(non_str)
2034
+ print(st)
2035
+
2036
+ # non_str2 = [val if not isinstance(val, str) else float(val) if val.isdigit() for val in values]
2037
+ # do it all manually cause too complex with list comprehension
2038
+ # a way of converting str to int
2039
+ for iii, v in enumerate(values):
2040
+ if isinstance(v, str):
2041
+ # if v.isdigit():
2042
+ # values[iii]=float(v)
2043
+ # else:
2044
+ # values[iii] = None
2045
+ # this one is better as it should keep the type
2046
+ if '.' in v:
2047
+ try:
2048
+ values[iii] = float(v)
2049
+ except ValueError:
2050
+ values[iii] = None
2051
+ else:
2052
+ try:
2053
+ values[iii] = int(v)
2054
+ except ValueError:
2055
+ values[iii] = None
2056
+
2057
+ print(values)
2058
+
2059
+ # pbs will occur when files are deleted in preview --> prevent delete button from working
2060
+ # TODO populate the other based on that
2061
+ # concat table name to the column name
2062
+ # assume everything can be plotted directly except a few columns that I would name and plot differently --> TODO
2063
+ # add correspondance local cell id and track ID to the database
2064
+ if True:
2065
+ # nb some images have different nb of cols --> does not fit...
2066
+ # masterDB = createMasterDB(['/E/Sample_images/sample_images_PA/mini/focused_Series012.png','/E/Sample_images/sample_images_PA/mini/focused_Series014.png','/E/Sample_images/sample_images_PA/mini/focused_Series015.png','/E/Sample_images/sample_images_PA/mini/focused_Series016.png'])
2067
+ # masterDB = createMasterDB(
2068
+ # ['/E/Sample_images/sample_images_PA/mini_different_nb_of_channels/focused_Series012.png',
2069
+ # '/E/Sample_images/sample_images_PA/mini_different_nb_of_channels/focused_Series014.png',
2070
+ # '/E/Sample_images/sample_images_PA/mini_different_nb_of_channels/focused_Series015.png',
2071
+ # '/E/Sample_images/sample_images_PA/mini_different_nb_of_channels/focused_Series016.png',
2072
+ # '/E/Sample_images/sample_images_PA/mini_different_nb_of_channels/focused_Series019.tif'])
2073
+
2074
+ # masterDB = createMasterDB(loadlist('/E/Sample_images/sample_images_PA/mini_different_nb_of_channels/list.lst'))
2075
+
2076
+
2077
+ outputName = None
2078
+ # outputName = '/E/Sample_images/sample_images_pyta/surface_projection/masterDB.db'
2079
+ masterDB = createMasterDB(loadlist('/E/Sample_images/sample_images_pyta/surface_projection/list.lst'), outputName=outputName, force_track_cells_db_update=True)
2080
+
2081
+
2082
+ if masterDB is not None:
2083
+ # masterDB = createMasterDB(['/E/Sample_images/sample_images_PA/mini/focused_Series012.png','/E/Sample_images/sample_images_PA/mini/focused_Series012.png','/E/Sample_images/sample_images_PA/mini/focused_Series012.png','/E/Sample_images/sample_images_PA/mini/focused_Series012.png'])
2084
+ print(masterDB.get_tables()) # why None
2085
+ # print(masterDB.run_SQL_command_and_get_results('SELECT * FROM cells_2D;')) # that seems to work
2086
+ # print(masterDB.get_min_max(table_name, 'area'))
2087
+ # print(masterDB.get_min_max(table_name, 'area', freq=[0.15,0.05])) # 0.1 --> est ce que ce sont des pourcentages ??? --> dois-je changer ma conversion --> sinon 10% --> may be a lot in fact --> ij fact ok
2088
+ # print(masterDB.get_table_column_names_and_types('bonds_2D'))
2089
+
2090
+ # print(masterDB.print_query('SELECT * FROM vertices_2D;'))
2091
+ # print(masterDB.print_query('SELECT * FROM cells_2D;'))
2092
+ # print(masterDB.print_query('SELECT frame_nb, filename, local_ID, cytoplasmic_area, area FROM cells_2D;'))
2093
+ # print(masterDB.print_query('SELECT frame_nb, filename, local_ID, sum_px_int_vertices_included_ch1, sum_px_int_vertices_included_ch3 FROM bonds_2D;')) # amyebe there is a bug in the db --> really need fix it, it should contain None and it does not --> I am doing a mistake --> fix it!!!
2094
+ # print(masterDB.get_table_column_names_and_types('cells_2D'))
2095
+ # print(masterDB.get_table_column_names_and_types('bonds_2D')) #
2096
+
2097
+ # print(masterDB.print_query('SELECT * FROM cells_2D NATURAL JOIN properties LIMIT 1'))
2098
+
2099
+
2100
+ # masterDB.drop_table('properties')
2101
+ # surprising that seems to work --> what if 0 DB inside
2102
+ # print(masterDB.print_query('SELECT * FROM cells_2D NATURAL JOIN properties')) # ça a l'air de marcher mais va t'il y avoir des bugs ??? sinon facile à gerer je pense
2103
+
2104
+ # how to make it work if the table does not exist at all --> did I take this into account --> # can I null it everywhere ????
2105
+
2106
+ # print(masterDB.print_query('SELECT * FROM cell_tracks NATURAL JOIN properties'))
2107
+ # print(masterDB.print_query('SELECT * FROM cell_tracks'))
2108
+ print(masterDB.print_query('SELECT * FROM cells_2D NATURAL JOIN cells_3D NATURAL JOIN cell_tracks NATURAL JOIN properties')) # ça a l'air de marcher mais va t'il y avoir des bugs ??? sinon facile à gerer je pense
2109
+
2110
+
2111
+ # print(masterDB.print_query('SELECT * FROM cells_2D NATURAL JOIN properties')) # ça a l'air de marcher mais va t'il y avoir des bugs ??? sinon facile à gerer je pense
2112
+
2113
+ print(masterDB.get_min_max('bonds_2D', 'sum_px_int_vertices_included_ch1')) # see how to exclude None values
2114
+ print(masterDB.get_min_max('bonds_2D', 'sum_px_int_vertices_included_ch1',
2115
+ ignore_None_and_string=False)) # see how to exclude None values
2116
+ print(masterDB.get_min_max('bonds_2D', 'sum_px_int_vertices_included_ch1', freq=[0.01, 0.01],
2117
+ ignore_None_and_string=False)) # see how to exclude None values
2118
+ print(masterDB.get_min_max('bonds_2D', 'sum_px_int_vertices_included_ch1', freq=[0.3, 0.01],
2119
+ ignore_None_and_string=False)) # see how to exclude None values
2120
+ print(masterDB.get_min_max('bonds_2D', 'sum_px_int_vertices_included_ch1', freq=[0.01, 0.01],
2121
+ ignore_None_and_string=True)) # see how to exclude None values
2122
+ # any way to print this ???
2123
+
2124
+
2125
+ # can I save an in mem table --> No
2126
+
2127
+ masterDB.close()
2128
+
2129
+ if True:
2130
+ sql_file = '/E/Sample_images/sample_images_PA/mini/focused_Series012/pyTA.db'
2131
+
2132
+ print(populate_table_content(sql_file))
2133
+
2134
+ table_name = 'cells_2D'
2135
+
2136
+ db = TAsql(sql_file) # maybe if file is none --> store in mem ???
2137
+ db.get_min_max(table_name, 'area')
2138
+ db.get_min_max(table_name, 'area', freq=[0.1, 0.1])
2139
+ print(db.get_table_column_names_and_types(table_name)) # what if two cols have same name ???
2140
+ db.clean()
2141
+ db.close()