|
9 | 9 |
|
10 | 10 | import numpy as np
|
11 | 11 | import pandas as pd
|
| 12 | + |
12 | 13 | import one.alf.io as alfio
|
13 | 14 |
|
14 | 15 | from ibllib.misc import check_nvidia_driver
|
|
24 | 25 | from ibllib.qc.camera import run_all_qc as run_camera_qc
|
25 | 26 | from ibllib.qc.dlc import DlcQC
|
26 | 27 | from ibllib.dsp import rms
|
| 28 | +from ibllib.plots.figures import dlc_qc_plot |
| 29 | +from ibllib.plots.snapshot import ReportSnapshot |
27 | 30 | from brainbox.behavior.dlc import likelihood_threshold, get_licks, get_pupil_diameter, get_smooth_pupil_diameter
|
28 | 31 |
|
29 | 32 | _logger = logging.getLogger("ibllib")
|
@@ -871,85 +874,104 @@ class EphysPostDLC(tasks.Task):
|
871 | 874 | ('_ibl_rightCamera.times.npy', 'alf', True),
|
872 | 875 | ('_ibl_leftCamera.times.npy', 'alf', True),
|
873 | 876 | ('_ibl_bodyCamera.times.npy', 'alf', True)],
|
| 877 | + # More files are required for all panels of the DLC QC plot to function |
874 | 878 | 'output_files': [('_ibl_leftCamera.features.pqt', 'alf', True),
|
875 | 879 | ('_ibl_rightCamera.features.pqt', 'alf', True),
|
876 | 880 | ('licks.times.npy', 'alf', True)]
|
877 | 881 | }
|
878 | 882 |
|
879 |
| - def _run(self, overwrite=False, run_qc=True): |
| 883 | + def _run(self, overwrite=False, run_qc=True, plot_qc=True): |
880 | 884 | # Check if output files exist locally
|
881 | 885 | exist, output_files = self.assert_expected(self.signature['output_files'], silent=True)
|
882 | 886 | if exist and not overwrite:
|
883 |
| - _logger.warning('EphysPostDLC outputs exist and overwrite=False, skipping.') |
884 |
| - return output_files |
885 |
| - if exist and overwrite: |
886 |
| - _logger.warning('EphysPostDLC outputs exist and overwrite=True, overwriting existing outputs.') |
887 |
| - # Find all available dlc traces and dlc times |
888 |
| - dlc_files = list(Path(self.session_path).joinpath('alf').glob('_ibl_*Camera.dlc.*')) |
889 |
| - for dlc_file in dlc_files: |
890 |
| - _logger.debug(dlc_file) |
891 |
| - output_files = [] |
892 |
| - combined_licks = [] |
893 |
| - |
894 |
| - for dlc_file in dlc_files: |
895 |
| - # Catch unforeseen exceptions and move on to next cam |
896 |
| - try: |
897 |
| - cam = label_from_path(dlc_file) |
898 |
| - # load dlc trace and camera times |
899 |
| - dlc = pd.read_parquet(dlc_file) |
900 |
| - dlc_thresh = likelihood_threshold(dlc, 0.9) |
901 |
| - # try to load respective camera times |
| 887 | + _logger.warning('EphysPostDLC outputs exist and overwrite=False, skipping computations of outputs.') |
| 888 | + else: |
| 889 | + if exist and overwrite: |
| 890 | + _logger.warning('EphysPostDLC outputs exist and overwrite=True, overwriting existing outputs.') |
| 891 | + # Find all available dlc traces and dlc times |
| 892 | + dlc_files = list(Path(self.session_path).joinpath('alf').glob('_ibl_*Camera.dlc.*')) |
| 893 | + for dlc_file in dlc_files: |
| 894 | + _logger.debug(dlc_file) |
| 895 | + output_files = [] |
| 896 | + combined_licks = [] |
| 897 | + |
| 898 | + for dlc_file in dlc_files: |
| 899 | + # Catch unforeseen exceptions and move on to next cam |
902 | 900 | try:
|
903 |
| - dlc_t = np.load(next(Path(self.session_path).joinpath('alf').glob(f'_ibl_{cam}Camera.times.*npy'))) |
904 |
| - times = True |
905 |
| - except StopIteration: |
906 |
| - _logger.error(f'No camera.times found for {cam} camera. ' |
907 |
| - f'Computations using camera.times will be skipped') |
908 |
| - self.status = -1 |
909 |
| - times = False |
910 |
| - |
911 |
| - # These features are only computed from left and right cam |
912 |
| - if cam in ('left', 'right'): |
913 |
| - features = pd.DataFrame() |
914 |
| - # If camera times are available, get the lick time stamps for combined array |
915 |
| - if times: |
916 |
| - _logger.info(f"Computing lick times for {cam} camera.") |
917 |
| - combined_licks.append(get_licks(dlc_thresh, dlc_t)) |
| 901 | + cam = label_from_path(dlc_file) |
| 902 | + # load dlc trace and camera times |
| 903 | + dlc = pd.read_parquet(dlc_file) |
| 904 | + dlc_thresh = likelihood_threshold(dlc, 0.9) |
| 905 | + # try to load respective camera times |
| 906 | + try: |
| 907 | + dlc_t = np.load(next(Path(self.session_path).joinpath('alf').glob(f'_ibl_{cam}Camera.times.*npy'))) |
| 908 | + times = True |
| 909 | + except StopIteration: |
| 910 | + _logger.error(f'No camera.times found for {cam} camera. ' |
| 911 | + f'Computations using camera.times will be skipped') |
| 912 | + self.status = -1 |
| 913 | + times = False |
| 914 | + |
| 915 | + # These features are only computed from left and right cam |
| 916 | + if cam in ('left', 'right'): |
| 917 | + features = pd.DataFrame() |
| 918 | + # If camera times are available, get the lick time stamps for combined array |
| 919 | + if times: |
| 920 | + _logger.info(f"Computing lick times for {cam} camera.") |
| 921 | + combined_licks.append(get_licks(dlc_thresh, dlc_t)) |
| 922 | + else: |
| 923 | + _logger.warning(f"Skipping lick times for {cam} camera as no camera.times available.") |
| 924 | + # Compute pupil diameter, raw and smoothed |
| 925 | + _logger.info(f"Computing raw pupil diameter for {cam} camera.") |
| 926 | + features['pupilDiameter_raw'] = get_pupil_diameter(dlc_thresh) |
| 927 | + _logger.info(f"Computing smooth pupil diameter for {cam} camera.") |
| 928 | + features['pupilDiameter_smooth'] = get_smooth_pupil_diameter(features['pupilDiameter_raw'], cam) |
| 929 | + # Safe to pqt |
| 930 | + features_file = Path(self.session_path).joinpath('alf', f'_ibl_{cam}Camera.features.pqt') |
| 931 | + features.to_parquet(features_file) |
| 932 | + output_files.append(features_file) |
| 933 | + |
| 934 | + # For all cams, compute DLC qc if times available |
| 935 | + if times and run_qc: |
| 936 | + # Setting download_data to False because at this point the data should be there |
| 937 | + qc = DlcQC(self.session_path, side=cam, one=self.one, download_data=False) |
| 938 | + qc.run(update=True) |
918 | 939 | else:
|
919 |
| - _logger.warning(f"Skipping lick times for {cam} camera as no camera.times available.") |
920 |
| - # Compute pupil diameter, raw and smoothed |
921 |
| - _logger.info(f"Computing raw pupil diameter for {cam} camera.") |
922 |
| - features['pupilDiameter_raw'] = get_pupil_diameter(dlc_thresh) |
923 |
| - _logger.info(f"Computing smooth pupil diameter for {cam} camera.") |
924 |
| - features['pupilDiameter_smooth'] = get_smooth_pupil_diameter(features['pupilDiameter_raw'], cam) |
925 |
| - # Safe to pqt |
926 |
| - features_file = Path(self.session_path).joinpath('alf', f'_ibl_{cam}Camera.features.pqt') |
927 |
| - features.to_parquet(features_file) |
928 |
| - output_files.append(features_file) |
929 |
| - |
930 |
| - # For all cams, compute DLC qc if times available |
931 |
| - if times and run_qc: |
932 |
| - # Setting download_data to False because at this point the data should be there |
933 |
| - qc = DlcQC(self.session_path, side=cam, one=self.one, download_data=False) |
934 |
| - qc.run(update=True) |
935 |
| - else: |
936 |
| - if not times: |
937 |
| - _logger.warning(f"Skipping QC for {cam} camera as no camera.times available") |
938 |
| - if not run_qc: |
939 |
| - _logger.warning(f"Skipping QC for {cam} camera as run_qc=False") |
| 940 | + if not times: |
| 941 | + _logger.warning(f"Skipping QC for {cam} camera as no camera.times available") |
| 942 | + if not run_qc: |
| 943 | + _logger.warning(f"Skipping QC for {cam} camera as run_qc=False") |
940 | 944 |
|
| 945 | + except BaseException: |
| 946 | + _logger.error(traceback.format_exc()) |
| 947 | + self.status = -1 |
| 948 | + continue |
| 949 | + |
| 950 | + # Combined lick times |
| 951 | + if len(combined_licks) > 0: |
| 952 | + lick_times_file = Path(self.session_path).joinpath('alf', 'licks.times.npy') |
| 953 | + np.save(lick_times_file, sorted(np.concatenate(combined_licks))) |
| 954 | + output_files.append(lick_times_file) |
| 955 | + else: |
| 956 | + _logger.warning("No lick times computed for this session.") |
| 957 | + |
| 958 | + if plot_qc: |
| 959 | + _logger.info("Creating DLC QC plot") |
| 960 | + try: |
| 961 | + session_id = self.one.path2eid(self.session_path) |
| 962 | + fig_path = self.session_path.joinpath('snapshot', 'dlc_qc_plot.png') |
| 963 | + if not fig_path.parent.exists(): |
| 964 | + fig_path.parent.mkdir(parents=True, exist_ok=True) |
| 965 | + fig = dlc_qc_plot(self.one.path2eid(self.session_path), one=self.one) |
| 966 | + fig.savefig(fig_path) |
| 967 | + snp = ReportSnapshot(self.session_path, session_id, one=self.one) |
| 968 | + snp.outputs = [fig_path] |
| 969 | + snp.register_images(widths=['orig'], |
| 970 | + function=str(dlc_qc_plot.__module__) + '.' + str(dlc_qc_plot.__name__)) |
941 | 971 | except BaseException:
|
| 972 | + _logger.error('Could not create and/or upload DLC QC Plot') |
942 | 973 | _logger.error(traceback.format_exc())
|
943 | 974 | self.status = -1
|
944 |
| - continue |
945 |
| - |
946 |
| - # Combined lick times |
947 |
| - if len(combined_licks) > 0: |
948 |
| - lick_times_file = Path(self.session_path).joinpath('alf', 'licks.times.npy') |
949 |
| - np.save(lick_times_file, sorted(np.concatenate(combined_licks))) |
950 |
| - output_files.append(lick_times_file) |
951 |
| - else: |
952 |
| - _logger.warning("No lick times computed for this session.") |
953 | 975 |
|
954 | 976 | return output_files
|
955 | 977 |
|
|
0 commit comments