DataBase methods - VoDEx Docs

# vodex.dbmethods module

## `DbExporter`

Transforms the information from the database into the core classes.

Source code in `src\vodex\dbmethods.py`

    1310
    1311
    1312
    1313
    1314
    1315
    1316
    1317
    1318
    1319
    1320
    1321
    1322
    1323
    1324
    1325
    1326
    1327
    1328
    1329
    1330
    1331
    1332
    1333
    1334
    1335
    1336
    1337
    1338
    1339
    1340
    1341
    1342
    1343
    1344
    1345
    1346
    1347
    1348
    1349
    1350
    1351
    1352
    1353
    1354
    1355
    1356
    1357
    1358
    1359
    1360
    1361
    1362
    1363
    1364
    1365
    1366
    1367
    1368
    1369
    1370
    1371
    1372
    1373
    1374
    1375
    1376
    1377
    1378
    1379
    1380
    1381
    1382
    1383
    1384
    1385
    1386
    1387
    1388
    1389
    1390
    1391
    1392
    1393
    1394
    1395
    1396
    1397
    1398
    1399
    1400
    1401
    1402
    1403
    1404
    1405
    1406
    1407
    1408
    1409
    1410
    1411
    1412
    1413
    1414
    1415
    1416
    class DbExporter:
        """
        Transforms the information from the database into the core classes.
        """

        # TODO : make it work for annotations

        def __init__(self, db_reader: DbReader):
            self.db = db_reader

        @classmethod
        def load(cls, file_name: Union[Path, str]):
            """
            Loads a database from a file and initialises a DbExporter.

            Args:
                file_name: full path to a file to database.
            """
            db_reader = DbReader.load(file_name)
            return cls(db_reader)

        def reconstruct_file_manager(self):
            """
            Creates file manager from the database records.
            """
            data_dir = self.db.get_data_dir()
            file_names = self.db.get_file_names()
            frames_per_file = self.db.get_frames_per_file()

            fm = FileManager(data_dir, file_names=file_names, frames_per_file=frames_per_file)
            return fm

        def reconstruct_volume_manager(self):
            """
            Creates volume manager from the database records.
            """
            fm = self.reconstruct_file_manager()
            fpv = self.db.get_fpv()
            fgf = self.db.get_fgf()
            vm = VolumeManager(fpv, FrameManager(fm), fgf=fgf)
            return vm

        def reconstruct_labels(self, group):
            """
            Creates labels corresponding to the specified annotation from the database records.
            """
            state_names, state_info = self.db.get_Name_and_Description_from_AnnotationTypeLabels(group)
            labels = Labels(group, state_names, state_info=state_info)
            return labels

        def reconstruct_timeline(self, group, labels):
            import itertools
            """
            Creates timeline corresponding to the specified annotation from the database records.
            """
            # get the mapping between the names and the Ids
            label_names = self.db.get_Id_map_to_Names_from_AnnotationTypeLabels()
            # get all the labels that were used in the annotation ( does not reconstruct unused Labels )
            labels_per_frame = self.db.get_AnnotationTypeLabelId_from_Annotations(group)

            label_order = []
            duration = []
            for label_id, frames in itertools.groupby(labels_per_frame):
                label_name = label_names[label_id]
                label_order.append(labels.__getattribute__(label_name))
                duration.append(sum(1 for _ in frames))

            timeline = Timeline(label_order, duration)
            return timeline

        def reconstruct_cycle(self, group):
            """
            Creates cycle corresponding to the specified annotation from the database records.
            """
            cycle_json = self.db.get_Structure_from_Cycle(group)
            if cycle_json is not None:
                cycle = Cycle.from_json(cycle_json)
            else:
                cycle = None
            return cycle

        def reconstruct_annotations(self):

            """
            Creates annotations from the database records.
            """
            # get the names of all the available annotations from the db
            annotation_names = self.db.get_Names_from_AnnotationTypes()
            # get the total number of frames in the recording
            n_frames = FrameManager(self.reconstruct_file_manager()).n_frames

            annotations = []
            for group in annotation_names:
                # reconstruct Labels for the group
                labels = self.reconstruct_labels(group)
                # try to get a cycle
                cycle = self.reconstruct_cycle(group)
                # define the annotation type ( Cycle / Timeline)
                if cycle is not None:
                    annotation = Annotation.from_cycle(n_frames, labels, cycle)
                else:
                    timeline = self.reconstruct_timeline(group, labels)
                    annotation = Annotation.from_timeline(n_frames, labels, timeline)

                annotations.append(annotation)

            return annotations

### `load(file_name)` `classmethod`

Loads a database from a file and initialises a DbExporter.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`file_name` | `Union[Path, str]` | full path to a file to database. | required

Source code in `src\vodex\dbmethods.py`

    1320
    1321
    1322
    1323
    1324
    1325
    1326
    1327
    1328
    1329
    @classmethod
    def load(cls, file_name: Union[Path, str]):
        """
        Loads a database from a file and initialises a DbExporter.

        Args:
            file_name: full path to a file to database.
        """
        db_reader = DbReader.load(file_name)
        return cls(db_reader)

### `reconstruct_annotations()`

Creates annotations from the database records.

Source code in `src\vodex\dbmethods.py`

    1391
    1392
    1393
    1394
    1395
    1396
    1397
    1398
    1399
    1400
    1401
    1402
    1403
    1404
    1405
    1406
    1407
    1408
    1409
    1410
    1411
    1412
    1413
    1414
    1415
    1416
    def reconstruct_annotations(self):

        """
        Creates annotations from the database records.
        """
        # get the names of all the available annotations from the db
        annotation_names = self.db.get_Names_from_AnnotationTypes()
        # get the total number of frames in the recording
        n_frames = FrameManager(self.reconstruct_file_manager()).n_frames

        annotations = []
        for group in annotation_names:
            # reconstruct Labels for the group
            labels = self.reconstruct_labels(group)
            # try to get a cycle
            cycle = self.reconstruct_cycle(group)
            # define the annotation type ( Cycle / Timeline)
            if cycle is not None:
                annotation = Annotation.from_cycle(n_frames, labels, cycle)
            else:
                timeline = self.reconstruct_timeline(group, labels)
                annotation = Annotation.from_timeline(n_frames, labels, timeline)

            annotations.append(annotation)

        return annotations

### `reconstruct_cycle(group)`

Creates cycle corresponding to the specified annotation from the database records.

Source code in `src\vodex\dbmethods.py`

    1380
    1381
    1382
    1383
    1384
    1385
    1386
    1387
    1388
    1389
    def reconstruct_cycle(self, group):
        """
        Creates cycle corresponding to the specified annotation from the database records.
        """
        cycle_json = self.db.get_Structure_from_Cycle(group)
        if cycle_json is not None:
            cycle = Cycle.from_json(cycle_json)
        else:
            cycle = None
        return cycle

### `reconstruct_file_manager()`

Creates file manager from the database records.

Source code in `src\vodex\dbmethods.py`

    1331
    1332
    1333
    1334
    1335
    1336
    1337
    1338
    1339
    1340
    def reconstruct_file_manager(self):
        """
        Creates file manager from the database records.
        """
        data_dir = self.db.get_data_dir()
        file_names = self.db.get_file_names()
        frames_per_file = self.db.get_frames_per_file()

        fm = FileManager(data_dir, file_names=file_names, frames_per_file=frames_per_file)
        return fm

### `reconstruct_labels(group)`

Creates labels corresponding to the specified annotation from the database records.

Source code in `src\vodex\dbmethods.py`

    1352
    1353
    1354
    1355
    1356
    1357
    1358
    def reconstruct_labels(self, group):
        """
        Creates labels corresponding to the specified annotation from the database records.
        """
        state_names, state_info = self.db.get_Name_and_Description_from_AnnotationTypeLabels(group)
        labels = Labels(group, state_names, state_info=state_info)
        return labels

### `reconstruct_volume_manager()`

Creates volume manager from the database records.

Source code in `src\vodex\dbmethods.py`

    1342
    1343
    1344
    1345
    1346
    1347
    1348
    1349
    1350
    def reconstruct_volume_manager(self):
        """
        Creates volume manager from the database records.
        """
        fm = self.reconstruct_file_manager()
        fpv = self.db.get_fpv()
        fgf = self.db.get_fgf()
        vm = VolumeManager(fpv, FrameManager(fm), fgf=fgf)
        return vm

## `DbReader`

Reads information from the database. Database interface that abstracts the SQLite calls.

Source code in `src\vodex\dbmethods.py`

     450
     451
     452
     453
     454
     455
     456
     457
     458
     459
     460
     461
     462
     463
     464
     465
     466
     467
     468
     469
     470
     471
     472
     473
     474
     475
     476
     477
     478
     479
     480
     481
     482
     483
     484
     485
     486
     487
     488
     489
     490
     491
     492
     493
     494
     495
     496
     497
     498
     499
     500
     501
     502
     503
     504
     505
     506
     507
     508
     509
     510
     511
     512
     513
     514
     515
     516
     517
     518
     519
     520
     521
     522
     523
     524
     525
     526
     527
     528
     529
     530
     531
     532
     533
     534
     535
     536
     537
     538
     539
     540
     541
     542
     543
     544
     545
     546
     547
     548
     549
     550
     551
     552
     553
     554
     555
     556
     557
     558
     559
     560
     561
     562
     563
     564
     565
     566
     567
     568
     569
     570
     571
     572
     573
     574
     575
     576
     577
     578
     579
     580
     581
     582
     583
     584
     585
     586
     587
     588
     589
     590
     591
     592
     593
     594
     595
     596
     597
     598
     599
     600
     601
     602
     603
     604
     605
     606
     607
     608
     609
     610
     611
     612
     613
     614
     615
     616
     617
     618
     619
     620
     621
     622
     623
     624
     625
     626
     627
     628
     629
     630
     631
     632
     633
     634
     635
     636
     637
     638
     639
     640
     641
     642
     643
     644
     645
     646
     647
     648
     649
     650
     651
     652
     653
     654
     655
     656
     657
     658
     659
     660
     661
     662
     663
     664
     665
     666
     667
     668
     669
     670
     671
     672
     673
     674
     675
     676
     677
     678
     679
     680
     681
     682
     683
     684
     685
     686
     687
     688
     689
     690
     691
     692
     693
     694
     695
     696
     697
     698
     699
     700
     701
     702
     703
     704
     705
     706
     707
     708
     709
     710
     711
     712
     713
     714
     715
     716
     717
     718
     719
     720
     721
     722
     723
     724
     725
     726
     727
     728
     729
     730
     731
     732
     733
     734
     735
     736
     737
     738
     739
     740
     741
     742
     743
     744
     745
     746
     747
     748
     749
     750
     751
     752
     753
     754
     755
     756
     757
     758
     759
     760
     761
     762
     763
     764
     765
     766
     767
     768
     769
     770
     771
     772
     773
     774
     775
     776
     777
     778
     779
     780
     781
     782
     783
     784
     785
     786
     787
     788
     789
     790
     791
     792
     793
     794
     795
     796
     797
     798
     799
     800
     801
     802
     803
     804
     805
     806
     807
     808
     809
     810
     811
     812
     813
     814
     815
     816
     817
     818
     819
     820
     821
     822
     823
     824
     825
     826
     827
     828
     829
     830
     831
     832
     833
     834
     835
     836
     837
     838
     839
     840
     841
     842
     843
     844
     845
     846
     847
     848
     849
     850
     851
     852
     853
     854
     855
     856
     857
     858
     859
     860
     861
     862
     863
     864
     865
     866
     867
     868
     869
     870
     871
     872
     873
     874
     875
     876
     877
     878
     879
     880
     881
     882
     883
     884
     885
     886
     887
     888
     889
     890
     891
     892
     893
     894
     895
     896
     897
     898
     899
     900
     901
     902
     903
     904
     905
     906
     907
     908
     909
     910
     911
     912
     913
     914
     915
     916
     917
     918
     919
     920
     921
     922
     923
     924
     925
     926
     927
     928
     929
     930
     931
     932
     933
     934
     935
     936
     937
     938
     939
     940
     941
     942
     943
     944
     945
     946
     947
     948
     949
     950
     951
     952
     953
     954
     955
     956
     957
     958
     959
     960
     961
     962
     963
     964
     965
     966
     967
     968
     969
     970
     971
     972
     973
     974
     975
     976
     977
     978
     979
     980
     981
     982
     983
     984
     985
     986
     987
     988
     989
     990
     991
     992
     993
     994
     995
     996
     997
     998
     999
    1000
    1001
    1002
    1003
    1004
    1005
    1006
    1007
    1008
    1009
    1010
    1011
    1012
    1013
    1014
    1015
    1016
    1017
    1018
    1019
    1020
    1021
    1022
    1023
    1024
    1025
    1026
    1027
    1028
    1029
    1030
    1031
    1032
    1033
    1034
    1035
    1036
    1037
    1038
    1039
    1040
    1041
    1042
    1043
    1044
    1045
    1046
    1047
    1048
    1049
    1050
    1051
    1052
    1053
    1054
    1055
    1056
    1057
    1058
    1059
    1060
    1061
    1062
    1063
    1064
    1065
    1066
    1067
    1068
    1069
    1070
    1071
    1072
    1073
    1074
    1075
    1076
    1077
    1078
    1079
    1080
    1081
    1082
    1083
    1084
    1085
    1086
    1087
    1088
    1089
    1090
    1091
    1092
    1093
    1094
    1095
    1096
    1097
    1098
    1099
    1100
    1101
    1102
    1103
    1104
    1105
    1106
    1107
    1108
    1109
    1110
    1111
    1112
    1113
    1114
    1115
    1116
    1117
    1118
    1119
    1120
    1121
    1122
    1123
    1124
    1125
    1126
    1127
    1128
    1129
    1130
    1131
    1132
    1133
    1134
    1135
    1136
    1137
    1138
    1139
    1140
    1141
    1142
    1143
    1144
    1145
    1146
    1147
    1148
    1149
    1150
    1151
    1152
    1153
    1154
    1155
    1156
    1157
    1158
    1159
    1160
    1161
    1162
    1163
    1164
    1165
    1166
    1167
    1168
    1169
    1170
    1171
    1172
    1173
    1174
    1175
    1176
    1177
    1178
    1179
    1180
    1181
    1182
    1183
    1184
    1185
    1186
    1187
    1188
    1189
    1190
    1191
    1192
    1193
    1194
    1195
    1196
    1197
    1198
    1199
    1200
    1201
    1202
    1203
    1204
    1205
    1206
    1207
    1208
    1209
    1210
    1211
    1212
    1213
    1214
    1215
    1216
    1217
    1218
    1219
    1220
    1221
    1222
    1223
    1224
    1225
    1226
    1227
    1228
    1229
    1230
    1231
    1232
    1233
    1234
    1235
    1236
    1237
    1238
    1239
    1240
    1241
    1242
    1243
    1244
    1245
    1246
    1247
    1248
    1249
    1250
    1251
    1252
    1253
    1254
    1255
    1256
    1257
    1258
    1259
    1260
    1261
    1262
    1263
    1264
    1265
    1266
    1267
    1268
    1269
    1270
    1271
    1272
    1273
    1274
    1275
    1276
    1277
    1278
    1279
    1280
    1281
    1282
    1283
    1284
    1285
    1286
    1287
    1288
    1289
    1290
    1291
    1292
    1293
    1294
    1295
    1296
    1297
    1298
    1299
    1300
    1301
    1302
    1303
    1304
    1305
    1306
    1307
    class DbReader:
        """
        Reads information from the database.
        Database interface that abstracts the SQLite calls.
        """

        def __init__(self, connection):
            self.connection = connection
            self.connection.execute("PRAGMA foreign_keys = 1")

        def get_n_frames(self):
            cursor = self.connection.cursor()
            try:
                cursor.execute(f"SELECT COUNT(*) FROM Frames")
                n_frames = cursor.fetchone()[0]
            except Exception as e:
                print(f"Could not get total number of frames from Frames because {e}")
                raise e
            finally:
                cursor.close()
            return n_frames

            # get the volumes

        def get_data_dir(self):
            """
            Get data directory
            """
            cursor = self.connection.cursor()
            try:
                cursor.execute(
                    f"""SELECT Value FROM Options
                         WHERE Key = "data_dir" """
                )
                data_dir = cursor.fetchone()[0]
            except Exception as e:
                print(f"Could not get_data_dir because {e}")
                raise e
            finally:
                cursor.close()

            return data_dir

        def get_fpv(self):
            """
            Get frames per volume
            """
            cursor = self.connection.cursor()
            try:
                cursor.execute(
                    f"""SELECT Value FROM Options
                         WHERE Key = "frames_per_volume" """
                )
                fpv = int(cursor.fetchone()[0])
            except Exception as e:
                print(f"Could not get_fpv because {e}")
                raise e
            finally:
                cursor.close()

            return fpv

        def get_fgf(self):
            """
            Get frames per volume
            """
            cursor = self.connection.cursor()
            try:
                cursor.execute(
                    f"""SELECT Value FROM Options
                         WHERE Key = "num_head_frames" """
                )
                fdf = int(cursor.fetchone()[0])
            except Exception as e:
                print(f"Could not get_fgf because {e}")
                raise e
            finally:
                cursor.close()

            return fdf

        def get_options(self):
            """
            Gets the information from the OPTIONS table.
            Returns a dictionary with the Key: Value from the table.
            ( Keys: 'data_dir', 'frames_per_volume', 'num_head_frames', 'num_tail_frames', 'num_full_volumes' )
            """
            cursor = self.connection.cursor()
            try:
                cursor.execute(
                    f"""SELECT * FROM Options"""
                )
                options = {}
                for row in cursor.fetchall():
                    options[row[0]] = row[1]

            except Exception as e:
                print(f"Could not get_options because {e}")
                raise e
            finally:
                cursor.close()

            return options

        def get_file_names(self):
            """
            Get the file names from the Files table.
            """
            cursor = self.connection.cursor()
            try:
                cursor.execute(
                    f"""SELECT FileName FROM Files ORDER BY Id ASC"""
                )
                file_names = [row[0] for row in cursor.fetchall()]
            except Exception as e:
                print(f"Could not get_file_names because {e}")
                raise e
            finally:
                cursor.close()

            return file_names

        def get_frames_per_file(self):
            """
            Get the file names from the Files table.
            """
            cursor = self.connection.cursor()
            try:
                cursor.execute(
                    f"""SELECT NumFrames FROM Files ORDER BY Id ASC"""
                )
                frames_per_file = [row[0] for row in cursor.fetchall()]
            except Exception as e:
                print(f"Could not get_frames_per_file because {e}")
                raise e
            finally:
                cursor.close()

            return frames_per_file

        def get_volume_list(self):
            """
            Returns a list of all the volumes.
            :return: list of all the volumes
            :rtype: [int]
            """
            cursor = self.connection.cursor()
            try:
                cursor.execute(f"SELECT DISTINCT VolumeId FROM Volumes")
                volume_ids = [volume[0] for volume in cursor.fetchall()]
            except Exception as e:
                print(f"Could not get total number of frames from Frames because {e}")
                raise e
            finally:
                cursor.close()

            return volume_ids

        @classmethod
        def load(cls, file_name):
            """
            Load the contents of a database file on disk to a
            transient copy in memory without modifying the file
            """
            disk_db = connect(file_name)
            memory_db = connect(':memory:')
            disk_db.backup(memory_db)
            disk_db.close()
            # Now use `memory_db` without modifying disk db
            return cls(memory_db)

        def choose_full_volumes(self, frames):
            """
            Chooses the frames from specified frames, that also correspond to full volumes.

            The order of the frames is not preserved!
            The result will correspond to frames sorted in increasing order !

            :param frames: a list of frame IDs
            :type frames: [int]
            :param fpv: frames per volume
            :type fpv: int
            :return: frames IDs from frames. corresponding to slices
            :rtype: [int]
            """
            # create list of frame Ids
            frame_ids = tuple(frames)
            n_frames = len(frame_ids)

            # get the volumes
            cursor = self.connection.cursor()
            try:
                # get n_volumes (frames per volume)
                cursor.execute(
                    f"""SELECT Value FROM Options
                        WHERE Key = "frames_per_volume" """
                )
                fpv = int(cursor.fetchone()[0])

                # get ids of full volumes in the provided frames
                cursor.execute(
                    f"""SELECT VolumeId FROM
                        (
                        SELECT VolumeId, count(VolumeId) as N
                        FROM Volumes
                        WHERE FrameId IN ({', '.join(['?'] * n_frames)})
                        GROUP BY VolumeId
                        )
                        WHERE N = ?""", frame_ids + (fpv,)
                )
                volume_ids = [volume[0] for volume in cursor.fetchall()]
                n_volumes = len(volume_ids)

                # get all frames from frames that correspond to full volumes
                cursor.execute(
                    f"""SELECT FrameId FROM Volumes
                        WHERE FrameId IN ({', '.join(['?'] * n_frames)})
                        AND VolumeId IN ({', '.join(['?'] * n_volumes)})""", frame_ids + tuple(volume_ids)
                )
                frame_ids = [frame[0] for frame in cursor.fetchall()]
            except Exception as e:
                print(f"Could not choose_full_volumes because {e}")
                raise e
            finally:
                cursor.close()

            return volume_ids, frame_ids

        def choose_frames_per_slices(self, frames, slices):
            """
            Chooses the frames from specified frames, that also correspond to the same slices (continuously)
            in different volumes.
            For example, if slices = [2,3,4] it will choose such frames from "given frames" that also correspond
            to a chunk from slice 2 to slice 4 in all the volumes.
            If there is a frame that corresponds to a slice "2" in a volume,
            but no frames corresponding to the slices "3" and "4" in the SAME volume, such frame will not be picked.

            The order of the frames is not preserved!
            The result will correspond to frames sorted in increasing order !

            :param frames: a list of frame IDs
            :type frames: [int]
            :param slices: a list of slice IDs, order will not be preserved: will be sorted in increasing order
            :type slices: [int]
            :return: frames IDs from frames. corresponding to slices
            :rtype: [int]
            """
            # create list of frame Ids
            frame_ids = tuple(frames)
            slice_ids = tuple(slices)
            n_frames = len(frame_ids)
            n_slices = len(slices)

            # get the volumes
            cursor = self.connection.cursor()
            try:
                cursor.execute(
                    f"""SELECT FrameId FROM Volumes
                        WHERE FrameId in ({', '.join(['?'] * n_frames)})
                        AND SliceInVolume IN ({', '.join(['?'] * n_slices)})
                        AND VolumeId IN
                            (
                            SELECT VolumeId FROM
                                (
                                SELECT VolumeId, count(VolumeId) as N
                                FROM Volumes
                                WHERE FrameId IN ({', '.join(['?'] * n_frames)})
                                AND SliceInVolume IN ({', '.join(['?'] * n_slices)})
                                GROUP BY VolumeId
                                )
                            WHERE N = ?
                            )""", frame_ids + slice_ids + frame_ids + slice_ids + (n_slices,)
                )

                frame_ids = cursor.fetchall()
            except Exception as e:
                print(f"Could not choose_frames_per_slices because {e}")
                raise e
            finally:
                cursor.close()
            frame_ids = [frame[0] for frame in frame_ids]
            return frame_ids

        def prepare_frames_for_loading(self, frames):
            """
            Finds all the information needed
            1) to load the frames
            2) and shape them back into volumes/slices.
            For each frame returns the image file,
            frame location on the image and corresponding volume.

            The order is not preserved!
            the volume correspond to sorted volumes in increasing order !

            :param frames: a list of frame IDs
            :type frames: [int]
            :return: three lists, the length of the frames :
                    data directory, file names,
                    image files, frame location on the image, volumes.
            :rtype: str, [str], [str],[int],[int]
            # TODO : break into functions that get the file locations and the stuff relevant to the frames
            # TODO : make one " prepare volumes " for loading?
            """
            n_frames = len(frames)

            # get the frames
            cursor = self.connection.cursor()
            try:
                # get data directory
                cursor.execute(
                    f"""SELECT Value
                        FROM Options
                        WHERE Key = "data_dir" """)
                data_dir = cursor.fetchone()[0]

                # get file_names
                cursor.execute(
                    f"""SELECT FileName
                        FROM Files """)
                file_names = cursor.fetchall()

                # get info for every frame
                cursor.execute(
                    f"""SELECT
                            FileId,
                            FrameInFile,
                            VolumeId
                        FROM
                            Frames
                            INNER JOIN Volumes
                            ON Frames.Id = Volumes.FrameId
                        WHERE FrameId IN ({', '.join(['?'] * n_frames)})""", tuple(frames))
                frame_info = cursor.fetchall()

            except Exception as e:
                print(f"Could not prepare_frames_for_loading because {e}")
                raise e
            finally:
                cursor.close()

            file_names = [row[0] for row in file_names]

            file_ids = [row[0] for row in frame_info]
            frame_in_file = [row[1] for row in frame_info]
            volumes = [row[2] for row in frame_info]

            return data_dir, file_names, file_ids, frame_in_file, volumes

        def get_frames_per_volumes(self, volume_ids):
            """
            Finds all the frames that correspond to the specified volumes.
            The order is not preserved!
            the volume correspond to sorted volumes in increasing order !

            :param volume_ids: a list of volume IDs
            :type volume_ids: [int]
            :return: frame IDs
            :rtype: [int]
            """
            ids = list_of_int(volume_ids)

            # get the frames
            cursor = self.connection.cursor()
            try:
                # create a parameterised query with variable number of parameters
                cursor.execute(
                    f"""SELECT FrameId FROM Volumes
                        WHERE VolumeId IN ({', '.join(['?'] * len(ids))})""", tuple(ids))
                frame_ids = cursor.fetchall()
            except Exception as e:
                print(f"Could not _get_FrameId_from_Volumes because {e}")
                raise e
            finally:
                cursor.close()
            # TODO : get rid of list of tuples?
            #  https://www.reddit.com/r/Python/comments/2iiqri/quick_question_why_are_sqlite_fields_returned_as/
            frame_ids = [frame[0] for frame in frame_ids]
            return frame_ids

        def get_and_frames_per_annotations(self, conditions):
            """
            Chooses the frames that correspond to the specified conditions on annotation. Using "and" logic. Example : if
            you ask for frames corresponding to condition 1 and condition 2 , it will return such frames that both
            condition 1 and condition 2 are True AT THE SAME TIME

            :param conditions: a list of conditions on the annotation labels
            in a form [(group, name),(group, name), ...] where group is a string for the annotation type
            and name is the name of the label of that annotation type. For example [('light', 'on'), ('shape','c')]
            :type conditions: [tuple]
            :return: list of frame Ids that satisfy all the conditions, if there are no such frames, an empty list
            :rtype: list
            """
            # create list of label Ids
            labels_ids = []
            for label_info in conditions:
                labels_ids.append(self._get_Id_from_AnnotationTypeLabels(label_info))
            labels_ids = tuple(labels_ids)
            n_labels = len(labels_ids)

            # get the frames
            cursor = self.connection.cursor()
            try:
                # create a parameterised query with variable number of parameters
                cursor.execute(
                    f"""SELECT FrameId FROM
                        (SELECT FrameId, count(FrameId) as N
                        FROM Annotations
                        WHERE AnnotationTypeLabelId IN ({', '.join(['?'] * n_labels)})
                        GROUP BY FrameId)
                        WHERE N = {n_labels}""", labels_ids)
                frame_ids = cursor.fetchall()
            except Exception as e:
                print(f"Could not _get_and_FrameId_from_Annotations because {e}")
                raise e
            finally:
                cursor.close()
            # TODO : get rid of list of tuples?
            #  https://www.reddit.com/r/Python/comments/2iiqri/quick_question_why_are_sqlite_fields_returned_as/
            frame_ids = [frame[0] for frame in frame_ids]
            return frame_ids

        def get_or_frames_per_annotations(self, conditions):
            """
            Chooses the frames that correspond to the specified conditions on annotation. Using "or" logic. Example : if
            you ask for frames corresponding to condition 1 and condition 2 , it will return such frames that either
            condition 1 is true OR condition 2 is True OR both are true.

            :param conditions: a list of conditions on the annotation labels
            in a form [(group, name),(group, name), ...] where group is a string for the annotation type
            and name is the name of the label of that annotation type. For example [('light', 'on'), ('shape','c')]
            :type conditions: [tuple]
            :return:
            :rtype:
            """
            # create list of label Ids
            labels_ids = []
            for label_info in conditions:
                labels_ids.append(self._get_Id_from_AnnotationTypeLabels(label_info))
            labels_ids = tuple(labels_ids)
            n_labels = len(labels_ids)

            # get the frames
            cursor = self.connection.cursor()
            try:
                # create a parameterised query with variable number of parameters
                cursor.execute(
                    f"""SELECT FrameId FROM Annotations
                        WHERE AnnotationTypeLabelId IN ({', '.join(['?'] * n_labels)})
                        GROUP BY FrameId""", labels_ids)
                frame_ids = cursor.fetchall()
            except Exception as e:
                print(f"Could not _get_or_FrameId_from_Annotations because {e}")
                raise e
            finally:
                cursor.close()
            # TODO : get rid of list of tuples?
            #  https://www.reddit.com/r/Python/comments/2iiqri/quick_question_why_are_sqlite_fields_returned_as/
            frame_ids = [frame[0] for frame in frame_ids]
            return frame_ids

        def get_conditionIds_per_cycle_per_volumes(self, annotation_name):
            """
            Returns a list of condition IDs that correspond to each volume, list of corresponding volumes
            and a count of the volume-condition pairs
            annotation_name: str
            """
            # TODO : check if empty
            cursor = self.connection.cursor()
            try:
                # create a parameterised query with variable number of parameters
                cursor.execute(
                    f"""SELECT VolumeId, AnnotationTypeLabelId, count(VolumeId) FROM
                        CycleIterations
                        INNER JOIN Volumes ON CycleIterations.FrameId = Volumes.FrameId
                        INNER JOIN Annotations ON CycleIterations.FrameId = Annotations.FrameId
                        WHERE AnnotationTypeLabelId in
                            (
                            SELECT Id from AnnotationTypeLabels
                            WHERE AnnotationTypeId = (SELECT Id from AnnotationTypes WHERE Name = ?)
                            )
                        AND CycleId = (SELECT Id from AnnotationTypes WHERE Name = ?)
                        AND CycleIteration = 0
                        GROUP BY VolumeId, AnnotationTypeLabelId
                        ORDER BY VolumeId""", (annotation_name, annotation_name))
                info = cursor.fetchall()
                # TODO : check if empty
                volume_ids = [row[0] for row in info]
                condition_ids = [row[1] for row in info]
                count = [row[2] for row in info]

            except Exception as e:
                print(f"Could not get_conditions_per_cycle_per_volumes because {e}")
                raise e
            finally:
                cursor.close()

            return volume_ids, condition_ids, count

        def get_conditionIds_per_cycle_per_frame(self, annotation_name):
            """
            Returns a list of condition IDs that correspond to each frame, list of corresponding frames.
            annotation_name: str
            """
            # TODO : check if empty
            cursor = self.connection.cursor()
            try:
                # create a parameterised query with variable number of parameters
                cursor.execute(
                    f"""SELECT CycleIterations.FrameId, AnnotationTypeLabelId FROM
                        CycleIterations
                        INNER JOIN Annotations ON CycleIterations.FrameId = Annotations.FrameId
                        WHERE AnnotationTypeLabelId in
                            (
                            SELECT Id from AnnotationTypeLabels
                            WHERE AnnotationTypeId = (SELECT Id from AnnotationTypes WHERE Name = ?)
                            )
                        AND CycleId = (SELECT Id from AnnotationTypes WHERE Name = ?)
                        AND CycleIteration = 0
                        ORDER BY CycleIterations.FrameId""", (annotation_name, annotation_name))
                info = cursor.fetchall()
                # TODO : check if empty
                frame_ids = [row[0] for row in info]
                condition_ids = [row[1] for row in info]

            except Exception as e:
                print(f"Could not get_conditionIds_per_cycle_per_frame because {e}")
                raise e
            finally:
                cursor.close()

            return frame_ids, condition_ids

        def get_cycleIterations_per_volumes(self, annotation_name):
            """
            Returns a list of cycleIterations that correspond to each volume, list of corresponding volumes
            and a count of the volume-iteration pairs
            annotation_name: str
            """
            # TODO : check if empty
            cursor = self.connection.cursor()
            try:
                # create a parameterised query with variable number of parameters
                cursor.execute(
                    f"""SELECT VolumeId, CycleIteration, count(VolumeId) FROM
                        CycleIterations
                        INNER JOIN Volumes ON CycleIterations.FrameId = Volumes.FrameId
                        INNER JOIN Annotations ON CycleIterations.FrameId = Annotations.FrameId
                        WHERE AnnotationTypeLabelId in
                            (
                            SELECT Id from AnnotationTypeLabels
                            WHERE AnnotationTypeId = (SELECT Id from AnnotationTypes WHERE Name = ?)
                            )
                        AND CycleId = (SELECT Id from AnnotationTypes WHERE Name = ?)
                        GROUP BY VolumeId
                        ORDER BY VolumeId""", (annotation_name, annotation_name))
                info = cursor.fetchall()
                # TODO : check if empty
                volume_ids = [row[0] for row in info]
                cycle_its = [row[1] for row in info]
                count = [row[2] for row in info]

            except Exception as e:
                print(f"Could not get_cycleIterations_per_volumes because {e}")
                raise e
            finally:
                cursor.close()

            return volume_ids, cycle_its, count

        def get_cycleIterations_per_frame(self, annotation_name):
            """
            Returns a list of cycle iterations that correspond to each frame, list of corresponding frames.
            annotation_name: str
            """
            # TODO : check if empty
            cursor = self.connection.cursor()
            try:
                # create a parameterised query with variable number of parameters
                cursor.execute(
                    f"""SELECT CycleIterations.FrameId, CycleIteration FROM
                        CycleIterations
                        INNER JOIN Annotations ON CycleIterations.FrameId = Annotations.FrameId
                        WHERE AnnotationTypeLabelId in
                            (
                            SELECT Id from AnnotationTypeLabels
                            WHERE AnnotationTypeId = (SELECT Id from AnnotationTypes WHERE Name = ?)
                            )
                        AND CycleId = (SELECT Id from AnnotationTypes WHERE Name = ?)
                        ORDER BY CycleIterations.FrameId""", (annotation_name, annotation_name))
                info = cursor.fetchall()
                # TODO : check if empty
                frame_ids = [row[0] for row in info]
                condition_ids = [row[1] for row in info]

            except Exception as e:
                print(f"Could not get_conditionIds_per_cycle_per_frame because {e}")
                raise e
            finally:
                cursor.close()

            return frame_ids, condition_ids

        def get_Names_from_AnnotationTypes(self):
            """
            Returns the names of all the available annotations.
            """
            cursor = self.connection.cursor()
            try:
                # create a parameterised query with variable number of parameters
                cursor.execute("""SELECT Name FROM AnnotationTypes""")
                names = [name[0] for name in cursor.fetchall()]
            except Exception as e:
                print(f"Could not _get_Name_from_AnnotationTypes because {e}")
                raise e
            finally:
                cursor.close()
            return names

        def get_Structure_from_Cycle(self, group):
            """
            Returns cycle Structure if the annotation has a cycle entry or None otherwise.
            """
            group = (group,)
            cursor = self.connection.cursor()
            try:
                # create a parameterised query with variable number of parameters
                cursor.execute("""SELECT Structure FROM Cycles WHERE AnnotationTypeId =
                                 (SELECT Id FROM AnnotationTypes WHERE Name = ?)""", group)
                info = cursor.fetchone()
                if info:
                    cycle = info[0]
                else:
                    cycle = None
            except Exception as e:
                print(f"Could not get_Structure_from_Cycle because {e}")
                raise e
            finally:
                cursor.close()
            return cycle

        def get_Name_and_Description_from_AnnotationTypeLabels(self, group):
            """
            Returns the Name and description for the labels that correspond to the annotation
            with the provided group name.
            """
            group = (group,)
            names = []
            descriptions = {}

            cursor = self.connection.cursor()
            try:
                # create a parameterised query with variable number of parameters
                cursor.execute("""SELECT Name, Description FROM AnnotationTypeLabels WHERE AnnotationTypeId =
                                 (SELECT Id FROM AnnotationTypes WHERE Name = ?)""", group)
                info = cursor.fetchall()
                for row in info:
                    name = row[0]
                    names.append(name)
                    descriptions[name] = row[1]
            except Exception as e:
                print(f"Could not get_Name_and_Description_from_AnnotationTypeLabels because {e}")
                raise e
            finally:
                cursor.close()
            return names, descriptions

        def _get_Names_from_AnnotationTypeLabels(self):
            cursor = self.connection.cursor()
            try:
                # create a parameterised query with variable number of parameters
                cursor.execute("""SELECT Name FROM AnnotationTypeLabels ORDER BY Id ASC""")
                names = [name[0] for name in cursor.fetchall()]
            except Exception as e:
                print(f"Could not _get_Name_from_AnnotationTypeLabels because {e}")
                raise e
            finally:
                cursor.close()
            return names

        def get_Id_map_to_Names_from_AnnotationTypeLabels(self):
            """
            Returns a dictionary with Ids as keys and names as values.
            """
            cursor = self.connection.cursor()
            mapping = {}
            try:
                # create a parameterised query with variable number of parameters
                cursor.execute("""SELECT Id, Name FROM AnnotationTypeLabels ORDER BY Id ASC""")
                info = cursor.fetchall()
                for row in info:
                    mapping[row[0]] = row[1]
            except Exception as e:
                print(f"Could not get_Id_map_to_Names_from_AnnotationTypeLabels because {e}")
                raise e
            finally:
                cursor.close()
            return mapping

        def _get_Id_from_AnnotationTypeLabels(self, label_info):
            """
            Returns the AnnotationTypeLabels.Id for a label , searching by its name and group name.
            :param label_info: (group, name), where group is the AnnotationType.Name
                                and name is AnnotationTypeLabels.Name
            :type label_info: tuple
            :return: AnnotationLabels.Id
            :rtype: int
            """
            cursor = self.connection.cursor()
            try:
                # create a parameterised query with variable number of parameters
                cursor.execute(
                    f"""SELECT Id FROM AnnotationTypeLabels
                        WHERE AnnotationTypeId = (SELECT Id from AnnotationTypes WHERE Name = ?)
                        and Name = ?""", label_info)
                label_id = cursor.fetchone()
                assert label_id is not None, f"Could not find a label from group {label_info[0]} " \
                                             f"with name {label_info[1]}. " \
                                             f"Are you sure it's been added into the database? "
            except Exception as e:
                print(f"Could not _get_AnnotationLabelId because {e}")
                raise e
            finally:
                cursor.close()
            return label_id[0]

        def _get_Ids_from_AnnotationTypeLabels(self, group):
            """
            Returns the AnnotationTypeLabels.Id for all labels , searching by their group name.
            :param group: AnnotationType.Name
            :type group: str
            :return: AnnotationLabels.Id
            :rtype: [(int,),(int,) ... ]
            """
            group = (group,)
            cursor = self.connection.cursor()
            try:
                # create a parameterised query with variable number of parameters
                cursor.execute(
                    f"""SELECT Id FROM AnnotationTypeLabels
                        WHERE AnnotationTypeId = (SELECT Id from AnnotationTypes WHERE Name = ?)""", group)
                label_ids = cursor.fetchall()
                assert label_ids is not None, f"Could not find labels from group {group} " \
                                              "Are you sure it's been added into the database? "
            except Exception as e:
                print(f"Could not _get_AnnotationLabelIds because {e}")
                raise e
            finally:
                cursor.close()
            return label_ids

        def get_AnnotationTypeLabelId_from_Annotations(self, group):
            """
            Returns the AnnotationTypeLabelIds for all labels in the order according to the FrameId,
             searching by their group name.
            :param group: AnnotationType.Name
            :type group: str
            :return: AnnotationLabels.Id
            :rtype: [int]
            """
            group = (group,)
            cursor = self.connection.cursor()
            try:
                # create a parameterised query with variable number of parameters
                cursor.execute(
                    f""" SELECT AnnotationTypeLabelId FROM Annotations WHERE AnnotationTypeLabelId IN
                                (SELECT Id FROM AnnotationTypeLabels
                                WHERE AnnotationTypeId = (SELECT Id from AnnotationTypes WHERE Name = ?))
                                ORDER BY FrameId ASC""", group)
                label_ids = [label[0] for label in cursor.fetchall()]
                assert label_ids is not None, f"Could not find labels from group {group} " \
                                              "Are you sure it's been added into the database? "
            except Exception as e:
                print(f"Could not _get_AnnotationTypeLabelId_from_Annotations because {e}")
                raise e
            finally:
                cursor.close()
            return label_ids

        def _get_SliceInVolume_from_Volumes(self, frames):
            """
            Chooses the slices that correspond to the specified frames.
            The order of the frames is not preserved!
            the volume correspond to frames sorted in increasing order !

            :param frames: a list of frame IDs
            :type frames: [int]
            :return: volume IDs
            :rtype: [int]
            """
            # create list of frame Ids
            frame_ids = tuple(frames)
            n_frames = len(frame_ids)

            # get the volumes
            cursor = self.connection.cursor()
            try:
                # create a parameterised query with variable number of parameters
                cursor.execute(
                    f"""SELECT SliceInVolume FROM Volumes
                        WHERE FrameId IN ({', '.join(['?'] * n_frames)})""", frame_ids)
                slice_ids = cursor.fetchall()
            except Exception as e:
                print(f"Could not _get_SliceInVolume_from_Volumes because {e}")
                raise e
            finally:
                cursor.close()
            slice_ids = [slice_id[0] for slice_id in slice_ids]
            return slice_ids

        def _get_VolumeId_from_Volumes(self, frames):
            """
            Chooses the volumes that correspond to the specified frames.
            The order is not preserved!
            the volume correspond to sorted frames in increasing order !

            :param frames: a list of frame IDs
            :type frames: [int]
            :return: volume IDs
            :rtype: [int]
            """
            # create list of frame Ids
            frame_ids = tuple(frames)
            n_frames = len(frame_ids)

            # get the volumes
            cursor = self.connection.cursor()
            try:
                # create a parameterised query with variable number of parameters
                cursor.execute(
                    f"""SELECT VolumeId FROM Volumes
                        WHERE FrameId IN ({', '.join(['?'] * n_frames)})""", frame_ids)
                volume_ids = cursor.fetchall()
            except Exception as e:
                print(f"Could not _get_VolumeId_from_Volumes because {e}")
                raise e
            finally:
                cursor.close()
            volume_ids = [volume[0] for volume in volume_ids]
            return volume_ids

        def delete_annotation(self, name: str):
            """
            Deletes annotation from all the tables.
            Deletion by "ON DELETE CASCADE" from AnnotationTypes, AnnotationTypeLabels, Annotations,
            Cycles, CycleIterations.
            """
            name = (name,)
            # get the volumes
            cursor = self.connection.cursor()
            try:
                cursor.execute(
                    f"""DELETE FROM AnnotationTypes
                                WHERE Name = ?""", name)
            except Exception as e:
                print(f"Could not delete_annotation because {e}")
                raise e
            finally:
                cursor.close()

### `choose_frames_per_slices(frames, slices)`

Chooses the frames from specified frames, that also correspond to the same slices (continuously) in different volumes. For example, if slices = [2,3,4] it will choose such frames from "given frames" that also correspond to a chunk from slice 2 to slice 4 in all the volumes. If there is a frame that corresponds to a slice "2" in a volume, but no frames corresponding to the slices "3" and "4" in the SAME volume, such frame will not be picked.

The order of the frames is not preserved! The result will correspond to frames sorted in increasing order !

:param frames: a list of frame IDs :type frames: [int] :param slices: a list of slice IDs, order will not be preserved: will be sorted in increasing order :type slices: [int] :return: frames IDs from frames. corresponding to slices :rtype: [int]

Source code in `src\vodex\dbmethods.py`

    678
    679
    680
    681
    682
    683
    684
    685
    686
    687
    688
    689
    690
    691
    692
    693
    694
    695
    696
    697
    698
    699
    700
    701
    702
    703
    704
    705
    706
    707
    708
    709
    710
    711
    712
    713
    714
    715
    716
    717
    718
    719
    720
    721
    722
    723
    724
    725
    726
    727
    728
    729
    730
    731
    def choose_frames_per_slices(self, frames, slices):
        """
        Chooses the frames from specified frames, that also correspond to the same slices (continuously)
        in different volumes.
        For example, if slices = [2,3,4] it will choose such frames from "given frames" that also correspond
        to a chunk from slice 2 to slice 4 in all the volumes.
        If there is a frame that corresponds to a slice "2" in a volume,
        but no frames corresponding to the slices "3" and "4" in the SAME volume, such frame will not be picked.

        The order of the frames is not preserved!
        The result will correspond to frames sorted in increasing order !

        :param frames: a list of frame IDs
        :type frames: [int]
        :param slices: a list of slice IDs, order will not be preserved: will be sorted in increasing order
        :type slices: [int]
        :return: frames IDs from frames. corresponding to slices
        :rtype: [int]
        """
        # create list of frame Ids
        frame_ids = tuple(frames)
        slice_ids = tuple(slices)
        n_frames = len(frame_ids)
        n_slices = len(slices)

        # get the volumes
        cursor = self.connection.cursor()
        try:
            cursor.execute(
                f"""SELECT FrameId FROM Volumes
                    WHERE FrameId in ({', '.join(['?'] * n_frames)})
                    AND SliceInVolume IN ({', '.join(['?'] * n_slices)})
                    AND VolumeId IN
                        (
                        SELECT VolumeId FROM
                            (
                            SELECT VolumeId, count(VolumeId) as N
                            FROM Volumes
                            WHERE FrameId IN ({', '.join(['?'] * n_frames)})
                            AND SliceInVolume IN ({', '.join(['?'] * n_slices)})
                            GROUP BY VolumeId
                            )
                        WHERE N = ?
                        )""", frame_ids + slice_ids + frame_ids + slice_ids + (n_slices,)
            )

            frame_ids = cursor.fetchall()
        except Exception as e:
            print(f"Could not choose_frames_per_slices because {e}")
            raise e
        finally:
            cursor.close()
        frame_ids = [frame[0] for frame in frame_ids]
        return frame_ids

### `choose_full_volumes(frames)`

Chooses the frames from specified frames, that also correspond to full volumes.

The order of the frames is not preserved! The result will correspond to frames sorted in increasing order !

:param frames: a list of frame IDs :type frames: [int] :param fpv: frames per volume :type fpv: int :return: frames IDs from frames. corresponding to slices :rtype: [int]

Source code in `src\vodex\dbmethods.py`

    621
    622
    623
    624
    625
    626
    627
    628
    629
    630
    631
    632
    633
    634
    635
    636
    637
    638
    639
    640
    641
    642
    643
    644
    645
    646
    647
    648
    649
    650
    651
    652
    653
    654
    655
    656
    657
    658
    659
    660
    661
    662
    663
    664
    665
    666
    667
    668
    669
    670
    671
    672
    673
    674
    675
    676
    def choose_full_volumes(self, frames):
        """
        Chooses the frames from specified frames, that also correspond to full volumes.

        The order of the frames is not preserved!
        The result will correspond to frames sorted in increasing order !

        :param frames: a list of frame IDs
        :type frames: [int]
        :param fpv: frames per volume
        :type fpv: int
        :return: frames IDs from frames. corresponding to slices
        :rtype: [int]
        """
        # create list of frame Ids
        frame_ids = tuple(frames)
        n_frames = len(frame_ids)

        # get the volumes
        cursor = self.connection.cursor()
        try:
            # get n_volumes (frames per volume)
            cursor.execute(
                f"""SELECT Value FROM Options
                    WHERE Key = "frames_per_volume" """
            )
            fpv = int(cursor.fetchone()[0])

            # get ids of full volumes in the provided frames
            cursor.execute(
                f"""SELECT VolumeId FROM
                    (
                    SELECT VolumeId, count(VolumeId) as N
                    FROM Volumes
                    WHERE FrameId IN ({', '.join(['?'] * n_frames)})
                    GROUP BY VolumeId
                    )
                    WHERE N = ?""", frame_ids + (fpv,)
            )
            volume_ids = [volume[0] for volume in cursor.fetchall()]
            n_volumes = len(volume_ids)

            # get all frames from frames that correspond to full volumes
            cursor.execute(
                f"""SELECT FrameId FROM Volumes
                    WHERE FrameId IN ({', '.join(['?'] * n_frames)})
                    AND VolumeId IN ({', '.join(['?'] * n_volumes)})""", frame_ids + tuple(volume_ids)
            )
            frame_ids = [frame[0] for frame in cursor.fetchall()]
        except Exception as e:
            print(f"Could not choose_full_volumes because {e}")
            raise e
        finally:
            cursor.close()

        return volume_ids, frame_ids

### `delete_annotation(name)`

Deletes annotation from all the tables. Deletion by "ON DELETE CASCADE" from AnnotationTypes, AnnotationTypeLabels, Annotations, Cycles, CycleIterations.

Source code in `src\vodex\dbmethods.py`

    1290
    1291
    1292
    1293
    1294
    1295
    1296
    1297
    1298
    1299
    1300
    1301
    1302
    1303
    1304
    1305
    1306
    1307
    def delete_annotation(self, name: str):
        """
        Deletes annotation from all the tables.
        Deletion by "ON DELETE CASCADE" from AnnotationTypes, AnnotationTypeLabels, Annotations,
        Cycles, CycleIterations.
        """
        name = (name,)
        # get the volumes
        cursor = self.connection.cursor()
        try:
            cursor.execute(
                f"""DELETE FROM AnnotationTypes
                            WHERE Name = ?""", name)
        except Exception as e:
            print(f"Could not delete_annotation because {e}")
            raise e
        finally:
            cursor.close()

### `get_AnnotationTypeLabelId_from_Annotations(group)`

Returns the AnnotationTypeLabelIds for all labels in the order according to the FrameId, searching by their group name. :param group: AnnotationType.Name :type group: str :return: AnnotationLabels.Id :rtype: [int]

Source code in `src\vodex\dbmethods.py`

    1200
    1201
    1202
    1203
    1204
    1205
    1206
    1207
    1208
    1209
    1210
    1211
    1212
    1213
    1214
    1215
    1216
    1217
    1218
    1219
    1220
    1221
    1222
    1223
    1224
    1225
    1226
    def get_AnnotationTypeLabelId_from_Annotations(self, group):
        """
        Returns the AnnotationTypeLabelIds for all labels in the order according to the FrameId,
         searching by their group name.
        :param group: AnnotationType.Name
        :type group: str
        :return: AnnotationLabels.Id
        :rtype: [int]
        """
        group = (group,)
        cursor = self.connection.cursor()
        try:
            # create a parameterised query with variable number of parameters
            cursor.execute(
                f""" SELECT AnnotationTypeLabelId FROM Annotations WHERE AnnotationTypeLabelId IN
                            (SELECT Id FROM AnnotationTypeLabels
                            WHERE AnnotationTypeId = (SELECT Id from AnnotationTypes WHERE Name = ?))
                            ORDER BY FrameId ASC""", group)
            label_ids = [label[0] for label in cursor.fetchall()]
            assert label_ids is not None, f"Could not find labels from group {group} " \
                                          "Are you sure it's been added into the database? "
        except Exception as e:
            print(f"Could not _get_AnnotationTypeLabelId_from_Annotations because {e}")
            raise e
        finally:
            cursor.close()
        return label_ids

### `get_Id_map_to_Names_from_AnnotationTypeLabels()`

Returns a dictionary with Ids as keys and names as values.

Source code in `src\vodex\dbmethods.py`

    1129
    1130
    1131
    1132
    1133
    1134
    1135
    1136
    1137
    1138
    1139
    1140
    1141
    1142
    1143
    1144
    1145
    1146
    def get_Id_map_to_Names_from_AnnotationTypeLabels(self):
        """
        Returns a dictionary with Ids as keys and names as values.
        """
        cursor = self.connection.cursor()
        mapping = {}
        try:
            # create a parameterised query with variable number of parameters
            cursor.execute("""SELECT Id, Name FROM AnnotationTypeLabels ORDER BY Id ASC""")
            info = cursor.fetchall()
            for row in info:
                mapping[row[0]] = row[1]
        except Exception as e:
            print(f"Could not get_Id_map_to_Names_from_AnnotationTypeLabels because {e}")
            raise e
        finally:
            cursor.close()
        return mapping

### `get_Name_and_Description_from_AnnotationTypeLabels(group)`

Returns the Name and description for the labels that correspond to the annotation with the provided group name.

Source code in `src\vodex\dbmethods.py`

    1090
    1091
    1092
    1093
    1094
    1095
    1096
    1097
    1098
    1099
    1100
    1101
    1102
    1103
    1104
    1105
    1106
    1107
    1108
    1109
    1110
    1111
    1112
    1113
    1114
    def get_Name_and_Description_from_AnnotationTypeLabels(self, group):
        """
        Returns the Name and description for the labels that correspond to the annotation
        with the provided group name.
        """
        group = (group,)
        names = []
        descriptions = {}

        cursor = self.connection.cursor()
        try:
            # create a parameterised query with variable number of parameters
            cursor.execute("""SELECT Name, Description FROM AnnotationTypeLabels WHERE AnnotationTypeId =
                             (SELECT Id FROM AnnotationTypes WHERE Name = ?)""", group)
            info = cursor.fetchall()
            for row in info:
                name = row[0]
                names.append(name)
                descriptions[name] = row[1]
        except Exception as e:
            print(f"Could not get_Name_and_Description_from_AnnotationTypeLabels because {e}")
            raise e
        finally:
            cursor.close()
        return names, descriptions

### `get_Names_from_AnnotationTypes()`

Returns the names of all the available annotations.

Source code in `src\vodex\dbmethods.py`

    1052
    1053
    1054
    1055
    1056
    1057
    1058
    1059
    1060
    1061
    1062
    1063
    1064
    1065
    1066
    def get_Names_from_AnnotationTypes(self):
        """
        Returns the names of all the available annotations.
        """
        cursor = self.connection.cursor()
        try:
            # create a parameterised query with variable number of parameters
            cursor.execute("""SELECT Name FROM AnnotationTypes""")
            names = [name[0] for name in cursor.fetchall()]
        except Exception as e:
            print(f"Could not _get_Name_from_AnnotationTypes because {e}")
            raise e
        finally:
            cursor.close()
        return names

### `get_Structure_from_Cycle(group)`

Returns cycle Structure if the annotation has a cycle entry or None otherwise.

Source code in `src\vodex\dbmethods.py`

    1068
    1069
    1070
    1071
    1072
    1073
    1074
    1075
    1076
    1077
    1078
    1079
    1080
    1081
    1082
    1083
    1084
    1085
    1086
    1087
    1088
    def get_Structure_from_Cycle(self, group):
        """
        Returns cycle Structure if the annotation has a cycle entry or None otherwise.
        """
        group = (group,)
        cursor = self.connection.cursor()
        try:
            # create a parameterised query with variable number of parameters
            cursor.execute("""SELECT Structure FROM Cycles WHERE AnnotationTypeId =
                             (SELECT Id FROM AnnotationTypes WHERE Name = ?)""", group)
            info = cursor.fetchone()
            if info:
                cycle = info[0]
            else:
                cycle = None
        except Exception as e:
            print(f"Could not get_Structure_from_Cycle because {e}")
            raise e
        finally:
            cursor.close()
        return cycle

### `get_and_frames_per_annotations(conditions)`

Chooses the frames that correspond to the specified conditions on annotation. Using "and" logic. Example : if you ask for frames corresponding to condition 1 and condition 2 , it will return such frames that both condition 1 and condition 2 are True AT THE SAME TIME

:param conditions: a list of conditions on the annotation labels in a form [(group, name),(group, name), ...] where group is a string for the annotation type and name is the name of the label of that annotation type. For example [('light', 'on'), ('shape','c')] :type conditions: [tuple] :return: list of frame Ids that satisfy all the conditions, if there are no such frames, an empty list :rtype: list

Source code in `src\vodex\dbmethods.py`

    829
    830
    831
    832
    833
    834
    835
    836
    837
    838
    839
    840
    841
    842
    843
    844
    845
    846
    847
    848
    849
    850
    851
    852
    853
    854
    855
    856
    857
    858
    859
    860
    861
    862
    863
    864
    865
    866
    867
    868
    869
    def get_and_frames_per_annotations(self, conditions):
        """
        Chooses the frames that correspond to the specified conditions on annotation. Using "and" logic. Example : if
        you ask for frames corresponding to condition 1 and condition 2 , it will return such frames that both
        condition 1 and condition 2 are True AT THE SAME TIME

        :param conditions: a list of conditions on the annotation labels
        in a form [(group, name),(group, name), ...] where group is a string for the annotation type
        and name is the name of the label of that annotation type. For example [('light', 'on'), ('shape','c')]
        :type conditions: [tuple]
        :return: list of frame Ids that satisfy all the conditions, if there are no such frames, an empty list
        :rtype: list
        """
        # create list of label Ids
        labels_ids = []
        for label_info in conditions:
            labels_ids.append(self._get_Id_from_AnnotationTypeLabels(label_info))
        labels_ids = tuple(labels_ids)
        n_labels = len(labels_ids)

        # get the frames
        cursor = self.connection.cursor()
        try:
            # create a parameterised query with variable number of parameters
            cursor.execute(
                f"""SELECT FrameId FROM
                    (SELECT FrameId, count(FrameId) as N
                    FROM Annotations
                    WHERE AnnotationTypeLabelId IN ({', '.join(['?'] * n_labels)})
                    GROUP BY FrameId)
                    WHERE N = {n_labels}""", labels_ids)
            frame_ids = cursor.fetchall()
        except Exception as e:
            print(f"Could not _get_and_FrameId_from_Annotations because {e}")
            raise e
        finally:
            cursor.close()
        # TODO : get rid of list of tuples?
        #  https://www.reddit.com/r/Python/comments/2iiqri/quick_question_why_are_sqlite_fields_returned_as/
        frame_ids = [frame[0] for frame in frame_ids]
        return frame_ids

### `get_conditionIds_per_cycle_per_frame(annotation_name)`

Returns a list of condition IDs that correspond to each frame, list of corresponding frames. annotation_name: str

Source code in `src\vodex\dbmethods.py`

    948
    949
    950
    951
    952
    953
    954
    955
    956
    957
    958
    959
    960
    961
    962
    963
    964
    965
    966
    967
    968
    969
    970
    971
    972
    973
    974
    975
    976
    977
    978
    979
    980
    def get_conditionIds_per_cycle_per_frame(self, annotation_name):
        """
        Returns a list of condition IDs that correspond to each frame, list of corresponding frames.
        annotation_name: str
        """
        # TODO : check if empty
        cursor = self.connection.cursor()
        try:
            # create a parameterised query with variable number of parameters
            cursor.execute(
                f"""SELECT CycleIterations.FrameId, AnnotationTypeLabelId FROM
                    CycleIterations
                    INNER JOIN Annotations ON CycleIterations.FrameId = Annotations.FrameId
                    WHERE AnnotationTypeLabelId in
                        (
                        SELECT Id from AnnotationTypeLabels
                        WHERE AnnotationTypeId = (SELECT Id from AnnotationTypes WHERE Name = ?)
                        )
                    AND CycleId = (SELECT Id from AnnotationTypes WHERE Name = ?)
                    AND CycleIteration = 0
                    ORDER BY CycleIterations.FrameId""", (annotation_name, annotation_name))
            info = cursor.fetchall()
            # TODO : check if empty
            frame_ids = [row[0] for row in info]
            condition_ids = [row[1] for row in info]

        except Exception as e:
            print(f"Could not get_conditionIds_per_cycle_per_frame because {e}")
            raise e
        finally:
            cursor.close()

        return frame_ids, condition_ids

### `get_conditionIds_per_cycle_per_volumes(annotation_name)`

Returns a list of condition IDs that correspond to each volume, list of corresponding volumes and a count of the volume-condition pairs annotation_name: str

Source code in `src\vodex\dbmethods.py`

    910
    911
    912
    913
    914
    915
    916
    917
    918
    919
    920
    921
    922
    923
    924
    925
    926
    927
    928
    929
    930
    931
    932
    933
    934
    935
    936
    937
    938
    939
    940
    941
    942
    943
    944
    945
    946
    def get_conditionIds_per_cycle_per_volumes(self, annotation_name):
        """
        Returns a list of condition IDs that correspond to each volume, list of corresponding volumes
        and a count of the volume-condition pairs
        annotation_name: str
        """
        # TODO : check if empty
        cursor = self.connection.cursor()
        try:
            # create a parameterised query with variable number of parameters
            cursor.execute(
                f"""SELECT VolumeId, AnnotationTypeLabelId, count(VolumeId) FROM
                    CycleIterations
                    INNER JOIN Volumes ON CycleIterations.FrameId = Volumes.FrameId
                    INNER JOIN Annotations ON CycleIterations.FrameId = Annotations.FrameId
                    WHERE AnnotationTypeLabelId in
                        (
                        SELECT Id from AnnotationTypeLabels
                        WHERE AnnotationTypeId = (SELECT Id from AnnotationTypes WHERE Name = ?)
                        )
                    AND CycleId = (SELECT Id from AnnotationTypes WHERE Name = ?)
                    AND CycleIteration = 0
                    GROUP BY VolumeId, AnnotationTypeLabelId
                    ORDER BY VolumeId""", (annotation_name, annotation_name))
            info = cursor.fetchall()
            # TODO : check if empty
            volume_ids = [row[0] for row in info]
            condition_ids = [row[1] for row in info]
            count = [row[2] for row in info]

        except Exception as e:
            print(f"Could not get_conditions_per_cycle_per_volumes because {e}")
            raise e
        finally:
            cursor.close()

        return volume_ids, condition_ids, count

### `get_cycleIterations_per_frame(annotation_name)`

Returns a list of cycle iterations that correspond to each frame, list of corresponding frames. annotation_name: str

Source code in `src\vodex\dbmethods.py`

    1019
    1020
    1021
    1022
    1023
    1024
    1025
    1026
    1027
    1028
    1029
    1030
    1031
    1032
    1033
    1034
    1035
    1036
    1037
    1038
    1039
    1040
    1041
    1042
    1043
    1044
    1045
    1046
    1047
    1048
    1049
    1050
    def get_cycleIterations_per_frame(self, annotation_name):
        """
        Returns a list of cycle iterations that correspond to each frame, list of corresponding frames.
        annotation_name: str
        """
        # TODO : check if empty
        cursor = self.connection.cursor()
        try:
            # create a parameterised query with variable number of parameters
            cursor.execute(
                f"""SELECT CycleIterations.FrameId, CycleIteration FROM
                    CycleIterations
                    INNER JOIN Annotations ON CycleIterations.FrameId = Annotations.FrameId
                    WHERE AnnotationTypeLabelId in
                        (
                        SELECT Id from AnnotationTypeLabels
                        WHERE AnnotationTypeId = (SELECT Id from AnnotationTypes WHERE Name = ?)
                        )
                    AND CycleId = (SELECT Id from AnnotationTypes WHERE Name = ?)
                    ORDER BY CycleIterations.FrameId""", (annotation_name, annotation_name))
            info = cursor.fetchall()
            # TODO : check if empty
            frame_ids = [row[0] for row in info]
            condition_ids = [row[1] for row in info]

        except Exception as e:
            print(f"Could not get_conditionIds_per_cycle_per_frame because {e}")
            raise e
        finally:
            cursor.close()

        return frame_ids, condition_ids

### `get_cycleIterations_per_volumes(annotation_name)`

Returns a list of cycleIterations that correspond to each volume, list of corresponding volumes and a count of the volume-iteration pairs annotation_name: str

Source code in `src\vodex\dbmethods.py`

     982
     983
     984
     985
     986
     987
     988
     989
     990
     991
     992
     993
     994
     995
     996
     997
     998
     999
    1000
    1001
    1002
    1003
    1004
    1005
    1006
    1007
    1008
    1009
    1010
    1011
    1012
    1013
    1014
    1015
    1016
    1017
    def get_cycleIterations_per_volumes(self, annotation_name):
        """
        Returns a list of cycleIterations that correspond to each volume, list of corresponding volumes
        and a count of the volume-iteration pairs
        annotation_name: str
        """
        # TODO : check if empty
        cursor = self.connection.cursor()
        try:
            # create a parameterised query with variable number of parameters
            cursor.execute(
                f"""SELECT VolumeId, CycleIteration, count(VolumeId) FROM
                    CycleIterations
                    INNER JOIN Volumes ON CycleIterations.FrameId = Volumes.FrameId
                    INNER JOIN Annotations ON CycleIterations.FrameId = Annotations.FrameId
                    WHERE AnnotationTypeLabelId in
                        (
                        SELECT Id from AnnotationTypeLabels
                        WHERE AnnotationTypeId = (SELECT Id from AnnotationTypes WHERE Name = ?)
                        )
                    AND CycleId = (SELECT Id from AnnotationTypes WHERE Name = ?)
                    GROUP BY VolumeId
                    ORDER BY VolumeId""", (annotation_name, annotation_name))
            info = cursor.fetchall()
            # TODO : check if empty
            volume_ids = [row[0] for row in info]
            cycle_its = [row[1] for row in info]
            count = [row[2] for row in info]

        except Exception as e:
            print(f"Could not get_cycleIterations_per_volumes because {e}")
            raise e
        finally:
            cursor.close()

        return volume_ids, cycle_its, count

### `get_data_dir()`

Get data directory

Source code in `src\vodex\dbmethods.py`

    474
    475
    476
    477
    478
    479
    480
    481
    482
    483
    484
    485
    486
    487
    488
    489
    490
    491
    def get_data_dir(self):
        """
        Get data directory
        """
        cursor = self.connection.cursor()
        try:
            cursor.execute(
                f"""SELECT Value FROM Options
                     WHERE Key = "data_dir" """
            )
            data_dir = cursor.fetchone()[0]
        except Exception as e:
            print(f"Could not get_data_dir because {e}")
            raise e
        finally:
            cursor.close()

        return data_dir

### `get_fgf()`

Get frames per volume

Source code in `src\vodex\dbmethods.py`

    512
    513
    514
    515
    516
    517
    518
    519
    520
    521
    522
    523
    524
    525
    526
    527
    528
    529
    def get_fgf(self):
        """
        Get frames per volume
        """
        cursor = self.connection.cursor()
        try:
            cursor.execute(
                f"""SELECT Value FROM Options
                     WHERE Key = "num_head_frames" """
            )
            fdf = int(cursor.fetchone()[0])
        except Exception as e:
            print(f"Could not get_fgf because {e}")
            raise e
        finally:
            cursor.close()

        return fdf

### `get_file_names()`

Get the file names from the Files table.

Source code in `src\vodex\dbmethods.py`

    554
    555
    556
    557
    558
    559
    560
    561
    562
    563
    564
    565
    566
    567
    568
    569
    570
    def get_file_names(self):
        """
        Get the file names from the Files table.
        """
        cursor = self.connection.cursor()
        try:
            cursor.execute(
                f"""SELECT FileName FROM Files ORDER BY Id ASC"""
            )
            file_names = [row[0] for row in cursor.fetchall()]
        except Exception as e:
            print(f"Could not get_file_names because {e}")
            raise e
        finally:
            cursor.close()

        return file_names

### `get_fpv()`

Get frames per volume

Source code in `src\vodex\dbmethods.py`

    493
    494
    495
    496
    497
    498
    499
    500
    501
    502
    503
    504
    505
    506
    507
    508
    509
    510
    def get_fpv(self):
        """
        Get frames per volume
        """
        cursor = self.connection.cursor()
        try:
            cursor.execute(
                f"""SELECT Value FROM Options
                     WHERE Key = "frames_per_volume" """
            )
            fpv = int(cursor.fetchone()[0])
        except Exception as e:
            print(f"Could not get_fpv because {e}")
            raise e
        finally:
            cursor.close()

        return fpv

### `get_frames_per_file()`

Get the file names from the Files table.

Source code in `src\vodex\dbmethods.py`

    572
    573
    574
    575
    576
    577
    578
    579
    580
    581
    582
    583
    584
    585
    586
    587
    588
    def get_frames_per_file(self):
        """
        Get the file names from the Files table.
        """
        cursor = self.connection.cursor()
        try:
            cursor.execute(
                f"""SELECT NumFrames FROM Files ORDER BY Id ASC"""
            )
            frames_per_file = [row[0] for row in cursor.fetchall()]
        except Exception as e:
            print(f"Could not get_frames_per_file because {e}")
            raise e
        finally:
            cursor.close()

        return frames_per_file

### `get_frames_per_volumes(volume_ids)`

Finds all the frames that correspond to the specified volumes. The order is not preserved! the volume correspond to sorted volumes in increasing order !

:param volume_ids: a list of volume IDs :type volume_ids: [int] :return: frame IDs :rtype: [int]

Source code in `src\vodex\dbmethods.py`

    798
    799
    800
    801
    802
    803
    804
    805
    806
    807
    808
    809
    810
    811
    812
    813
    814
    815
    816
    817
    818
    819
    820
    821
    822
    823
    824
    825
    826
    827
    def get_frames_per_volumes(self, volume_ids):
        """
        Finds all the frames that correspond to the specified volumes.
        The order is not preserved!
        the volume correspond to sorted volumes in increasing order !

        :param volume_ids: a list of volume IDs
        :type volume_ids: [int]
        :return: frame IDs
        :rtype: [int]
        """
        ids = list_of_int(volume_ids)

        # get the frames
        cursor = self.connection.cursor()
        try:
            # create a parameterised query with variable number of parameters
            cursor.execute(
                f"""SELECT FrameId FROM Volumes
                    WHERE VolumeId IN ({', '.join(['?'] * len(ids))})""", tuple(ids))
            frame_ids = cursor.fetchall()
        except Exception as e:
            print(f"Could not _get_FrameId_from_Volumes because {e}")
            raise e
        finally:
            cursor.close()
        # TODO : get rid of list of tuples?
        #  https://www.reddit.com/r/Python/comments/2iiqri/quick_question_why_are_sqlite_fields_returned_as/
        frame_ids = [frame[0] for frame in frame_ids]
        return frame_ids

### `get_options()`

Gets the information from the OPTIONS table. Returns a dictionary with the Key: Value from the table. ( Keys: 'data_dir', 'frames_per_volume', 'num_head_frames', 'num_tail_frames', 'num_full_volumes' )

Source code in `src\vodex\dbmethods.py`

    531
    532
    533
    534
    535
    536
    537
    538
    539
    540
    541
    542
    543
    544
    545
    546
    547
    548
    549
    550
    551
    552
    def get_options(self):
        """
        Gets the information from the OPTIONS table.
        Returns a dictionary with the Key: Value from the table.
        ( Keys: 'data_dir', 'frames_per_volume', 'num_head_frames', 'num_tail_frames', 'num_full_volumes' )
        """
        cursor = self.connection.cursor()
        try:
            cursor.execute(
                f"""SELECT * FROM Options"""
            )
            options = {}
            for row in cursor.fetchall():
                options[row[0]] = row[1]

        except Exception as e:
            print(f"Could not get_options because {e}")
            raise e
        finally:
            cursor.close()

        return options

### `get_or_frames_per_annotations(conditions)`

Chooses the frames that correspond to the specified conditions on annotation. Using "or" logic. Example : if you ask for frames corresponding to condition 1 and condition 2 , it will return such frames that either condition 1 is true OR condition 2 is True OR both are true.

:param conditions: a list of conditions on the annotation labels in a form [(group, name),(group, name), ...] where group is a string for the annotation type and name is the name of the label of that annotation type. For example [('light', 'on'), ('shape','c')] :type conditions: [tuple] :return: :rtype:

Source code in `src\vodex\dbmethods.py`

    871
    872
    873
    874
    875
    876
    877
    878
    879
    880
    881
    882
    883
    884
    885
    886
    887
    888
    889
    890
    891
    892
    893
    894
    895
    896
    897
    898
    899
    900
    901
    902
    903
    904
    905
    906
    907
    908
    def get_or_frames_per_annotations(self, conditions):
        """
        Chooses the frames that correspond to the specified conditions on annotation. Using "or" logic. Example : if
        you ask for frames corresponding to condition 1 and condition 2 , it will return such frames that either
        condition 1 is true OR condition 2 is True OR both are true.

        :param conditions: a list of conditions on the annotation labels
        in a form [(group, name),(group, name), ...] where group is a string for the annotation type
        and name is the name of the label of that annotation type. For example [('light', 'on'), ('shape','c')]
        :type conditions: [tuple]
        :return:
        :rtype:
        """
        # create list of label Ids
        labels_ids = []
        for label_info in conditions:
            labels_ids.append(self._get_Id_from_AnnotationTypeLabels(label_info))
        labels_ids = tuple(labels_ids)
        n_labels = len(labels_ids)

        # get the frames
        cursor = self.connection.cursor()
        try:
            # create a parameterised query with variable number of parameters
            cursor.execute(
                f"""SELECT FrameId FROM Annotations
                    WHERE AnnotationTypeLabelId IN ({', '.join(['?'] * n_labels)})
                    GROUP BY FrameId""", labels_ids)
            frame_ids = cursor.fetchall()
        except Exception as e:
            print(f"Could not _get_or_FrameId_from_Annotations because {e}")
            raise e
        finally:
            cursor.close()
        # TODO : get rid of list of tuples?
        #  https://www.reddit.com/r/Python/comments/2iiqri/quick_question_why_are_sqlite_fields_returned_as/
        frame_ids = [frame[0] for frame in frame_ids]
        return frame_ids

### `get_volume_list()`

Returns a list of all the volumes. :return: list of all the volumes :rtype: [int]

Source code in `src\vodex\dbmethods.py`

    590
    591
    592
    593
    594
    595
    596
    597
    598
    599
    600
    601
    602
    603
    604
    605
    606
    def get_volume_list(self):
        """
        Returns a list of all the volumes.
        :return: list of all the volumes
        :rtype: [int]
        """
        cursor = self.connection.cursor()
        try:
            cursor.execute(f"SELECT DISTINCT VolumeId FROM Volumes")
            volume_ids = [volume[0] for volume in cursor.fetchall()]
        except Exception as e:
            print(f"Could not get total number of frames from Frames because {e}")
            raise e
        finally:
            cursor.close()

        return volume_ids

### `load(file_name)` `classmethod`

Load the contents of a database file on disk to a transient copy in memory without modifying the file

Source code in `src\vodex\dbmethods.py`

    608
    609
    610
    611
    612
    613
    614
    615
    616
    617
    618
    619
    @classmethod
    def load(cls, file_name):
        """
        Load the contents of a database file on disk to a
        transient copy in memory without modifying the file
        """
        disk_db = connect(file_name)
        memory_db = connect(':memory:')
        disk_db.backup(memory_db)
        disk_db.close()
        # Now use `memory_db` without modifying disk db
        return cls(memory_db)

### `prepare_frames_for_loading(frames)`

Finds all the information needed 1) to load the frames 2) and shape them back into volumes/slices. For each frame returns the image file, frame location on the image and corresponding volume.

The order is not preserved! the volume correspond to sorted volumes in increasing order !

:param frames: a list of frame IDs :type frames: [int] :return: three lists, the length of the frames : data directory, file names, image files, frame location on the image, volumes. :rtype: str, [str], [str],[int],[int]

#### TODO : break into functions that get the file locations and the stuff relevant to the frames

#### TODO : make one " prepare volumes " for loading?

Source code in `src\vodex\dbmethods.py`

    733
    734
    735
    736
    737
    738
    739
    740
    741
    742
    743
    744
    745
    746
    747
    748
    749
    750
    751
    752
    753
    754
    755
    756
    757
    758
    759
    760
    761
    762
    763
    764
    765
    766
    767
    768
    769
    770
    771
    772
    773
    774
    775
    776
    777
    778
    779
    780
    781
    782
    783
    784
    785
    786
    787
    788
    789
    790
    791
    792
    793
    794
    795
    796
    def prepare_frames_for_loading(self, frames):
        """
        Finds all the information needed
        1) to load the frames
        2) and shape them back into volumes/slices.
        For each frame returns the image file,
        frame location on the image and corresponding volume.

        The order is not preserved!
        the volume correspond to sorted volumes in increasing order !

        :param frames: a list of frame IDs
        :type frames: [int]
        :return: three lists, the length of the frames :
                data directory, file names,
                image files, frame location on the image, volumes.
        :rtype: str, [str], [str],[int],[int]
        # TODO : break into functions that get the file locations and the stuff relevant to the frames
        # TODO : make one " prepare volumes " for loading?
        """
        n_frames = len(frames)

        # get the frames
        cursor = self.connection.cursor()
        try:
            # get data directory
            cursor.execute(
                f"""SELECT Value
                    FROM Options
                    WHERE Key = "data_dir" """)
            data_dir = cursor.fetchone()[0]

            # get file_names
            cursor.execute(
                f"""SELECT FileName
                    FROM Files """)
            file_names = cursor.fetchall()

            # get info for every frame
            cursor.execute(
                f"""SELECT
                        FileId,
                        FrameInFile,
                        VolumeId
                    FROM
                        Frames
                        INNER JOIN Volumes
                        ON Frames.Id = Volumes.FrameId
                    WHERE FrameId IN ({', '.join(['?'] * n_frames)})""", tuple(frames))
            frame_info = cursor.fetchall()

        except Exception as e:
            print(f"Could not prepare_frames_for_loading because {e}")
            raise e
        finally:
            cursor.close()

        file_names = [row[0] for row in file_names]

        file_ids = [row[0] for row in frame_info]
        frame_in_file = [row[1] for row in frame_info]
        volumes = [row[2] for row in frame_info]

        return data_dir, file_names, file_ids, frame_in_file, volumes

## `DbWriter`

Writes information to the database. Database interface that abstracts the SQLite calls.

Learning resources

sql: https://www.devdungeon.com/content/python-sqlite3-tutorial https://www.sqlitetutorial.net/sqlite-python/creating-tables/ try-catch : https://softwareengineering.stackexchange.com/questions/64180/good-use-of-try-catch-blocks

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`connection` |  | connection to the SQL database | required

Source code in `src\vodex\dbmethods.py`

      9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    366
    367
    368
    369
    370
    371
    372
    373
    374
    375
    376
    377
    378
    379
    380
    381
    382
    383
    384
    385
    386
    387
    388
    389
    390
    391
    392
    393
    394
    395
    396
    397
    398
    399
    400
    401
    402
    403
    404
    405
    406
    407
    408
    409
    410
    411
    412
    413
    414
    415
    416
    417
    418
    419
    420
    421
    422
    423
    424
    425
    426
    427
    428
    429
    430
    431
    432
    433
    434
    435
    436
    437
    438
    439
    440
    441
    442
    443
    444
    445
    446
    447
    class DbWriter:
        """
        Writes information to the database.
        Database interface that abstracts the SQLite calls.

        Learning resources:
            sql:
            https://www.devdungeon.com/content/python-sqlite3-tutorial
            https://www.sqlitetutorial.net/sqlite-python/creating-tables/
            try-catch :
            https://softwareengineering.stackexchange.com/questions/64180/good-use-of-try-catch-blocks

        Args:
            connection: connection to the SQL database
        """

        def __init__(self, connection):
            self.connection = connection
            self.connection.execute("PRAGMA foreign_keys = 1")

        def save(self, file_name: str):
            """
            Backup a database to a file.
            Will CLOSE connection to the database in memory!
            Args:
                file_name : the name of the file to save database to, EX.: 'databasename.db'
            """

            def progress(status, remaining, total):
                print(f'Copied {total - remaining} of {total} pages...')

            backup_db = connect(file_name)
            with backup_db:
                self.connection.backup(backup_db, progress=progress)
            # self.connection.close()
            backup_db.close()

        @classmethod
        def create(cls):
            """
            Creates an empty DB for the experiment in memory.
            """
            # TODO : figure out how to type-annotate classmethod
            # For an in-memory only database:
            memory_db = connect(':memory:')
            return cls(memory_db)

        @classmethod
        def load(cls, file_name: str):
            """
            Load the contents of a database file on disk to a
            transient copy in memory without modifying the file.
            """
            disk_db = connect(file_name)
            memory_db = connect(':memory:')
            disk_db.backup(memory_db)
            disk_db.close()
            # Now use `memory_db` without modifying disk db
            return cls(memory_db)

        def populate(self, volumes: VolumeManager = None, annotations: List[Annotation] = None):
            """
            Creates the tables if they don't exist and fills with the provided data.
            Args:
                volumes: mapping of frames to volumes, and to slices in volumes, frames per volume
                annotations: mapping of frames to labels, list of annotations
            """
            # TODO : add warnings when you are trying to write the same data again
            # TODO : How to type annotate vodex classes from core ?

            # will only create if they don't exist
            self._create_tables()
            # TODO : write cases for files and frames not None

            if volumes is not None:
                self._populate_Options(volumes.file_manager, volumes)
                self._populate_Files(volumes.file_manager)
                self._populate_Frames(volumes.frame_manager)
                self._populate_Volumes(volumes)

            if annotations is not None:
                self.add_annotations(annotations)

        def add_annotations(self, annotations: List[Annotation]):
            """
            Adds a list of annotations to the database.
            Does NOT save the database after adding.
            To keep this change in the future, you need to save the database after adding.
            Args:
                annotations: mapping of frames to labels, list of annotations
            """
            # TODO : add list checking
            for annotation in annotations:
                self._populate_AnnotationTypes(annotation)
                self._populate_AnnotationTypeLabels(annotation)
                self._populate_Annotations(annotation)
                if annotation.cycle is not None:
                    self._populate_Cycles(annotation)
                    self._populate_CycleIterations(annotation)

        def get_n_frames(self) -> int:
            """
            Queries and returns the total number of frames in the experiment.
            """
            cursor = self.connection.cursor()
            try:
                cursor.execute(f"SELECT COUNT(*) FROM Frames")
                n_frames = cursor.fetchone()[0]
            except Exception as e:
                print(f"Could not get total number of frames from Frames because {e}")
                raise e
            finally:
                cursor.close()
            return n_frames

        def _create_tables(self):

            # TODO : change UNIQUE(a, b)
            # into primary key over both columns a and b where appropriate

            db_cursor = self.connection.cursor()

            sql_create_Options_table = """
                CREATE TABLE IF NOT EXISTS "Options" (
                "Key"	TEXT NOT NULL UNIQUE,
                "Value"	TEXT NOT NULL,
                "Description"	TEXT,
                PRIMARY KEY("Key")
                )
                """
            db_cursor.execute(sql_create_Options_table)

            sql_create_Files_table = """
                CREATE TABLE IF NOT EXISTS "Files" (
                "Id"	INTEGER NOT NULL UNIQUE,
                "FileName"	TEXT NOT NULL UNIQUE,
                "NumFrames"	INTEGER NOT NULL,
                PRIMARY KEY("Id" AUTOINCREMENT)
                )
                """
            db_cursor.execute(sql_create_Files_table)

            sql_create_AnnotationTypes_table = """
                CREATE TABLE IF NOT EXISTS "AnnotationTypes" (
                "Id"	INTEGER NOT NULL UNIQUE,
                "Name"	TEXT NOT NULL UNIQUE,
                "Description"	TEXT,
                PRIMARY KEY("Id" AUTOINCREMENT)
                )
                """
            db_cursor.execute(sql_create_AnnotationTypes_table)

            sql_create_Frames_table = """
                CREATE TABLE IF NOT EXISTS "Frames" (
                "Id"	INTEGER NOT NULL UNIQUE,
                "FrameInFile"	INTEGER NOT NULL,
                "FileId"	INTEGER NOT NULL,
                PRIMARY KEY("Id" AUTOINCREMENT),
                FOREIGN KEY("FileId") REFERENCES "Files"("Id")
                )
                """
            db_cursor.execute(sql_create_Frames_table)

            sql_create_Cycles_table = """
                CREATE TABLE IF NOT EXISTS "Cycles" (
                "Id"	INTEGER NOT NULL UNIQUE,
                "AnnotationTypeId"	INTEGER NOT NULL UNIQUE,
                "Structure"	TEXT NOT NULL,
                FOREIGN KEY("AnnotationTypeId") REFERENCES "AnnotationTypes"("Id") ON DELETE CASCADE,
                PRIMARY KEY("Id" AUTOINCREMENT)
                )
                """
            db_cursor.execute(sql_create_Cycles_table)

            sql_create_AnnotationTypeLabels_table = """
                CREATE TABLE IF NOT EXISTS "AnnotationTypeLabels" (
                "Id"	INTEGER NOT NULL UNIQUE,
                "AnnotationTypeId"	INTEGER NOT NULL,
                "Name"	TEXT NOT NULL,
                "Description"	TEXT,
                PRIMARY KEY("Id" AUTOINCREMENT),
                FOREIGN KEY("AnnotationTypeId") REFERENCES "AnnotationTypes"("Id") ON DELETE CASCADE,
                UNIQUE("AnnotationTypeId","Name")
                )
                """
            db_cursor.execute(sql_create_AnnotationTypeLabels_table)

            sql_create_Annotations_table = """
                CREATE TABLE IF NOT EXISTS "Annotations" (
                "FrameId"	INTEGER NOT NULL,
                "AnnotationTypeLabelId"	INTEGER NOT NULL,
                FOREIGN KEY("FrameId") REFERENCES "Frames"("Id"),
                FOREIGN KEY("AnnotationTypeLabelId") REFERENCES "AnnotationTypeLabels"("Id") ON DELETE CASCADE,
                UNIQUE("FrameId","AnnotationTypeLabelId")
                )
                """
            db_cursor.execute(sql_create_Annotations_table)

            sql_create_CycleIterations_table = """
                CREATE TABLE IF NOT EXISTS "CycleIterations" (
                "FrameId"	INTEGER NOT NULL,
                "CycleId"	INTEGER NOT NULL,
                "CycleIteration"	INTEGER NOT NULL,
                FOREIGN KEY("CycleId") REFERENCES "Cycles"("Id") ON DELETE CASCADE,
                FOREIGN KEY("FrameId") REFERENCES "Frames"("Id")
                )
                """
            db_cursor.execute(sql_create_CycleIterations_table)

            sql_create_Volumes_table = """
                CREATE TABLE IF NOT EXISTS "Volumes" (
                "FrameId"	INTEGER NOT NULL UNIQUE,
                "VolumeId"	INTEGER NOT NULL,
                "SliceInVolume"	INTEGER NOT NULL,
                PRIMARY KEY("FrameId" AUTOINCREMENT),
                FOREIGN KEY("FrameId") REFERENCES "Frames"("Id")
                UNIQUE("VolumeId","SliceInVolume")
                )
                """
            db_cursor.execute(sql_create_Volumes_table)

            db_cursor.close()

        def _populate_Options(self, file_manager: FileManager, volume_manager: VolumeManager):
            """
            Populates the Options table (a dictionary with key - value pairs).
            Learning resources:
                another way of dealing with Errors : ( more pretty ??? )
                https://www.w3resource.com/python-exercises/sqlite/python-sqlite-exercise-6.php
            Args:
                file_manager: FileManager object that provides the data to populate the tables.
                volume_manager: VolumeManager object that provides the data to populate the tables.
            """
            row_data = [("data_dir", file_manager.data_dir.as_posix()),
                        ("frames_per_volume", volume_manager.fpv),
                        ("num_head_frames", volume_manager.n_head),
                        ("num_tail_frames", volume_manager.n_tail),
                        ("num_full_volumes", volume_manager.full_volumes)]

            cursor = self.connection.cursor()
            try:
                cursor.executemany(
                    "INSERT INTO Options (Key, Value) VALUES (?, ?)",
                    row_data)
                self.connection.commit()
            except Exception as e:
                print(f"Could not write to Options because {e}")
                raise e
            finally:
                cursor.close()

        def _populate_Files(self, file_manager: FileManager):
            """
            Populates the Files table with
            file_name : list with filenames per file (str), and
            num_frames : list with number of frames per file (int)
            Args:
                file_manager: FileManager object that provides the data to populate the tables.
            """
            row_data = [(filename, frames) for
                        filename, frames in zip(file_manager.file_names, file_manager.num_frames)]

            cursor = self.connection.cursor()
            try:
                cursor.executemany(
                    "INSERT INTO Files (FileName, NumFrames) VALUES (?, ?)",
                    row_data)
                self.connection.commit()
            except Exception as e:
                print(f"Could not write to Files because {e}")
                raise e
            finally:
                cursor.close()

        def _populate_Frames(self, frame_manager: FrameManager):
            """
            Populates the Frames table with
            frame_in_file and frame_to_file mapping.

            Learning Resourses:
                Something like
                insert into tab2 (id_customers, value)
                values ((select id from tab1 where customers='john'), 'alfa');
                but in SQLite
                https://www.tutorialspoint.com/sqlite/sqlite_insert_query.htm

            Args:
                frame_manager: FrameManager object that provides the data to populate the tables.
            """
            # adding +1 since the frame_to_file is indexing files from 0 and sqlite gave files IDs from 1
            row_data = [(frame_in_file, frame_to_file + 1) for
                        frame_in_file, frame_to_file in zip(frame_manager.frame_in_file, frame_manager.frame_to_file)]

            cursor = self.connection.cursor()
            try:
                cursor.executemany(
                    "INSERT INTO Frames (FrameInFile, FileId) VALUES (?, ?)",
                    row_data)
                self.connection.commit()
            except Exception as e:
                print(f"Could not write to Frames because {e}")
                raise e
            finally:
                cursor.close()

        def _populate_Volumes(self, volume_manager: VolumeManager):
            """
            Populates the Volumes table with
            volume_id and slice_in_volume mapping.

            Args:
                volume_manager: VolumeManager object that provides the data to populate the tables.
            """
            row_data = [(volume_id, slice_in_volume) for
                        volume_id, slice_in_volume in zip(volume_manager.frame_to_vol, volume_manager.frame_to_z)]

            cursor = self.connection.cursor()
            try:
                cursor.executemany(
                    "INSERT INTO Volumes (VolumeId, SliceInVolume) VALUES (?, ?)",
                    row_data)
                self.connection.commit()
            except Exception as e:
                print(f"Could not write to Volumes because {e}")
                raise e
            finally:
                cursor.close()

        def _populate_AnnotationTypes(self, annotation: Annotation):
            """
            Populates the AnnotationTypes table with
            annotation.name, annotation.info mapping.

            Args:
                volume_manager: VolumeManager object that provides the data to populate the tables.
            """
            row_data = (annotation.name, annotation.info)
            cursor = self.connection.cursor()
            try:
                cursor.execute(
                    "INSERT INTO AnnotationTypes (Name, Description) VALUES (?, ?)",
                    row_data)
                self.connection.commit()
            except Exception as e:
                print(f"Could not write to AnnotationTypes because {e}")
                raise e
            finally:
                cursor.close()

        def _populate_AnnotationTypeLabels(self, annotation):

            row_data = [(label.group, label.name, label.description)
                        for label in annotation.labels.states]

            cursor = self.connection.cursor()
            try:
                cursor.executemany(
                    "INSERT INTO AnnotationTypeLabels (AnnotationTypeId, Name, Description) " +
                    "VALUES((SELECT Id FROM AnnotationTypes WHERE Name = ?), ?, ?)",
                    row_data)
                self.connection.commit()
            except Exception as e:
                print(f"Could not write to AnnotationTypeLabels because {e}")
                raise e
            finally:
                cursor.close()

        def _populate_Annotations(self, annotation):
            n_frames = self.get_n_frames()
            assert n_frames == annotation.n_frames, f"Number of frames in the annotation, {annotation.n_frames}," \
                                                    f"doesn't match" \
                                                    f" the expected number of frames {n_frames}"
            frames = range(n_frames)
            row_data = [(frame + 1, label.name, label.group)
                        for frame, label in zip(frames, annotation.frame_to_label)]
            cursor = self.connection.cursor()
            try:
                cursor.executemany(
                    "INSERT INTO Annotations (FrameId, AnnotationTypeLabelId) " +
                    "VALUES(?, (SELECT Id FROM AnnotationTypeLabels "
                    "WHERE Name = ? " +
                    "AND AnnotationTypeId = (SELECT Id FROM AnnotationTypes " +
                    "WHERE Name = ?)))",
                    row_data)
                self.connection.commit()
            except Exception as e:
                print(f"Could not write group {annotation.name} to Annotations because {e}")
                raise e
            finally:
                cursor.close()

        def _populate_Cycles(self, annotation):
            """
            """
            row_data = (annotation.name, annotation.cycle.to_json())
            cursor = self.connection.cursor()
            try:
                cursor.execute(
                    "INSERT INTO Cycles (AnnotationTypeId, Structure) " +
                    "VALUES((SELECT Id FROM AnnotationTypes WHERE Name = ?), ?)",
                    row_data)
                self.connection.commit()
            except Exception as e:
                print(f"Could not write to Cycles because {e}")
                raise e
            finally:
                cursor.close()

        def _populate_CycleIterations(self, annotation):
            n_frames = self.get_n_frames()
            assert n_frames == annotation.n_frames, f"Number of frames in the annotation, {annotation.n_frames}," \
                                                    f"doesn't match" \
                                                    f" the expected number of frames {n_frames}"
            # get cycle id by annotation type
            cursor = self.connection.cursor()
            cursor.execute(
                "SELECT Id FROM Cycles " +
                "WHERE AnnotationTypeId = (SELECT Id FROM AnnotationTypes " +
                "WHERE Name = ?)", (annotation.name,))
            cycle_id = cursor.fetchone()[0]
            cursor.close()

            # prepare rows
            frames = range(n_frames)
            row_data = [(frame + 1, cycle_id, iteration)
                        for frame, iteration in zip(frames, annotation.frame_to_cycle)]

            # insert into CycleIterations
            cursor = self.connection.cursor()
            try:
                cursor.executemany(
                    "INSERT INTO CycleIterations (FrameId, CycleId, CycleIteration) " +
                    "VALUES(?, ?,?)", row_data)
                self.connection.commit()
            except Exception as e:
                print(f"Could not write group {annotation.name} to CycleIterations because {e}")
                raise e
            finally:
                cursor.close()

### `add_annotations(annotations)`

Adds a list of annotations to the database. Does NOT save the database after adding. To keep this change in the future, you need to save the database after adding.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`annotations` | `List[Annotation]` | mapping of frames to labels, list of annotations | required

Source code in `src\vodex\dbmethods.py`

     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    107
    def add_annotations(self, annotations: List[Annotation]):
        """
        Adds a list of annotations to the database.
        Does NOT save the database after adding.
        To keep this change in the future, you need to save the database after adding.
        Args:
            annotations: mapping of frames to labels, list of annotations
        """
        # TODO : add list checking
        for annotation in annotations:
            self._populate_AnnotationTypes(annotation)
            self._populate_AnnotationTypeLabels(annotation)
            self._populate_Annotations(annotation)
            if annotation.cycle is not None:
                self._populate_Cycles(annotation)
                self._populate_CycleIterations(annotation)

### `create()` `classmethod`

Creates an empty DB for the experiment in memory.

Source code in `src\vodex\dbmethods.py`

    46
    47
    48
    49
    50
    51
    52
    53
    54
    @classmethod
    def create(cls):
        """
        Creates an empty DB for the experiment in memory.
        """
        # TODO : figure out how to type-annotate classmethod
        # For an in-memory only database:
        memory_db = connect(':memory:')
        return cls(memory_db)

### `get_n_frames()`

Queries and returns the total number of frames in the experiment.

Source code in `src\vodex\dbmethods.py`

    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    def get_n_frames(self) -> int:
        """
        Queries and returns the total number of frames in the experiment.
        """
        cursor = self.connection.cursor()
        try:
            cursor.execute(f"SELECT COUNT(*) FROM Frames")
            n_frames = cursor.fetchone()[0]
        except Exception as e:
            print(f"Could not get total number of frames from Frames because {e}")
            raise e
        finally:
            cursor.close()
        return n_frames

### `load(file_name)` `classmethod`

Load the contents of a database file on disk to a transient copy in memory without modifying the file.

Source code in `src\vodex\dbmethods.py`

    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    @classmethod
    def load(cls, file_name: str):
        """
        Load the contents of a database file on disk to a
        transient copy in memory without modifying the file.
        """
        disk_db = connect(file_name)
        memory_db = connect(':memory:')
        disk_db.backup(memory_db)
        disk_db.close()
        # Now use `memory_db` without modifying disk db
        return cls(memory_db)

### `populate(volumes=None, annotations=None)`

Creates the tables if they don't exist and fills with the provided data.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`volumes` | `VolumeManager` | mapping of frames to volumes, and to slices in volumes, frames per volume | `None`
`annotations` | `List[Annotation]` | mapping of frames to labels, list of annotations | `None`

Source code in `src\vodex\dbmethods.py`

    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    def populate(self, volumes: VolumeManager = None, annotations: List[Annotation] = None):
        """
        Creates the tables if they don't exist and fills with the provided data.
        Args:
            volumes: mapping of frames to volumes, and to slices in volumes, frames per volume
            annotations: mapping of frames to labels, list of annotations
        """
        # TODO : add warnings when you are trying to write the same data again
        # TODO : How to type annotate vodex classes from core ?

        # will only create if they don't exist
        self._create_tables()
        # TODO : write cases for files and frames not None

        if volumes is not None:
            self._populate_Options(volumes.file_manager, volumes)
            self._populate_Files(volumes.file_manager)
            self._populate_Frames(volumes.frame_manager)
            self._populate_Volumes(volumes)

        if annotations is not None:
            self.add_annotations(annotations)

### `save(file_name)`

Backup a database to a file. Will CLOSE connection to the database in memory!

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`file_name` |  | the name of the file to save database to, EX.: 'databasename.db' | required

Source code in `src\vodex\dbmethods.py`

    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    def save(self, file_name: str):
        """
        Backup a database to a file.
        Will CLOSE connection to the database in memory!
        Args:
            file_name : the name of the file to save database to, EX.: 'databasename.db'
        """

        def progress(status, remaining, total):
            print(f'Copied {total - remaining} of {total} pages...')

        backup_db = connect(file_name)
        with backup_db:
            self.connection.backup(backup_db, progress=progress)
        # self.connection.close()
        backup_db.close()

#### Keyboard Shortcuts

Keys | Action
--- | ---
`?` | Open this help
`n` | Next page
`p` | Previous page
`s` | Search