|
1 | 1 | import logging |
2 | 2 | import subprocess |
| 3 | +import time |
3 | 4 | import traceback |
4 | 5 | from pathlib import Path |
5 | 6 |
|
@@ -610,3 +611,118 @@ def _run(self, overwrite=True, run_qc=True, plot_qc=True): |
610 | 611 | self.status = -1 |
611 | 612 |
|
612 | 613 | return output_files |
| 614 | + |
| 615 | + |
| 616 | +class LightningPose(base_tasks.VideoTask): |
| 617 | + # TODO: make one task per cam? |
| 618 | + gpu = 1 |
| 619 | + io_charge = 100 |
| 620 | + level = 2 |
| 621 | + force = True |
| 622 | + job_size = 'large' |
| 623 | + |
| 624 | + env = Path.home().joinpath('Documents', 'PYTHON', 'envs', 'litpose', 'bin', 'activate') |
| 625 | + scripts = Path.home().joinpath('Documents', 'PYTHON', 'iblscripts', 'deploy', 'serverpc', 'litpose') |
| 626 | + |
| 627 | + @property |
| 628 | + def signature(self): |
| 629 | + signature = { |
| 630 | + 'input_files': [(f'_iblrig_{cam}Camera.raw.mp4', self.device_collection, True) for cam in self.cameras], |
| 631 | + 'output_files': [(f'_ibl_{cam}Camera.lightningPose.pqt', 'alf', True) for cam in self.cameras] |
| 632 | + } |
| 633 | + |
| 634 | + return signature |
| 635 | + |
| 636 | + @staticmethod |
| 637 | + def _video_intact(file_mp4): |
| 638 | + """Checks that the downloaded video can be opened and is not empty""" |
| 639 | + cap = cv2.VideoCapture(str(file_mp4)) |
| 640 | + frame_count = cap.get(cv2.CAP_PROP_FRAME_COUNT) |
| 641 | + intact = True if frame_count > 0 else False |
| 642 | + cap.release() |
| 643 | + return intact |
| 644 | + |
| 645 | + def _check_env(self): |
| 646 | + """Check that scripts are present, env can be activated and get iblvideo version""" |
| 647 | + assert len(list(self.scripts.rglob('run_litpose.*'))) == 2, \ |
| 648 | + f'Scripts run_litpose.sh and run_litpose.py do not exist in {self.scripts}' |
| 649 | + assert self.env.exists(), f"environment does not exist in assumed location {self.env}" |
| 650 | + command2run = f"source {self.env}; python -c 'import iblvideo; print(iblvideo.__version__)'" |
| 651 | + process = subprocess.Popen( |
| 652 | + command2run, |
| 653 | + shell=True, |
| 654 | + stdout=subprocess.PIPE, |
| 655 | + stderr=subprocess.PIPE, |
| 656 | + executable="/bin/bash" |
| 657 | + ) |
| 658 | + info, error = process.communicate() |
| 659 | + if process.returncode != 0: |
| 660 | + raise AssertionError(f"environment check failed\n{error.decode('utf-8')}") |
| 661 | + version = info.decode("utf-8").strip().split('\n')[-1] |
| 662 | + return version |
| 663 | + |
| 664 | + def _run(self, overwrite=True, **kwargs): |
| 665 | + |
| 666 | + # Gather video files |
| 667 | + self.session_path = Path(self.session_path) |
| 668 | + mp4_files = [ |
| 669 | + self.session_path.joinpath(self.device_collection, f'_iblrig_{cam}Camera.raw.mp4') for cam in self.cameras |
| 670 | + if self.session_path.joinpath(self.device_collection, f'_iblrig_{cam}Camera.raw.mp4').exists() |
| 671 | + ] |
| 672 | + |
| 673 | + labels = [label_from_path(x) for x in mp4_files] |
| 674 | + _logger.info(f'Running on {labels} videos') |
| 675 | + |
| 676 | + # Check the environment |
| 677 | + self.version = self._check_env() |
| 678 | + _logger.info(f'iblvideo version {self.version}') |
| 679 | + |
| 680 | + # If all results exist and overwrite is False, skip computation |
| 681 | + expected_outputs_present, expected_outputs = self.assert_expected(self.output_files, silent=True) |
| 682 | + if overwrite is False and expected_outputs_present is True: |
| 683 | + actual_outputs = expected_outputs |
| 684 | + return actual_outputs |
| 685 | + |
| 686 | + # Else, loop over videos |
| 687 | + actual_outputs = [] |
| 688 | + for label, mp4_file in zip(labels, mp4_files): |
| 689 | + # Catch exceptions so that the other cams can still run but set status to Errored |
| 690 | + try: |
| 691 | + # Check that the GPU is (still) accessible |
| 692 | + check_nvidia_driver() |
| 693 | + # Check that the video can be loaded |
| 694 | + if not self._video_intact(mp4_file): |
| 695 | + _logger.error(f"Corrupt raw video file {mp4_file}") |
| 696 | + self.status = -1 |
| 697 | + continue |
| 698 | + t0 = time.time() |
| 699 | + _logger.info(f'Running Lightning Pose on {label}Camera.') |
| 700 | + command2run = f"{self.scripts.joinpath('run_litpose.sh')} {str(self.env)} {mp4_file} {overwrite}" |
| 701 | + _logger.info(command2run) |
| 702 | + process = subprocess.Popen( |
| 703 | + command2run, |
| 704 | + shell=True, |
| 705 | + stdout=subprocess.PIPE, |
| 706 | + stderr=subprocess.PIPE, |
| 707 | + executable="/bin/bash", |
| 708 | + ) |
| 709 | + info, error = process.communicate() |
| 710 | + if process.returncode != 0: |
| 711 | + error_str = error.decode("utf-8").strip() |
| 712 | + _logger.error(f'Lightning pose failed for {label}Camera.\n\n' |
| 713 | + f'++++++++ Output of subprocess for debugging ++++++++\n\n' |
| 714 | + f'{error_str}\n' |
| 715 | + f'++++++++++++++++++++++++++++++++++++++++++++\n') |
| 716 | + self.status = -1 |
| 717 | + continue |
| 718 | + else: |
| 719 | + _logger.info(f'{label} camera took {(time.time() - t0)} seconds') |
| 720 | + result = next(self.session_path.joinpath('alf').glob(f'_ibl_{label}Camera.lightningPose*.pqt')) |
| 721 | + actual_outputs.append(result) |
| 722 | + |
| 723 | + except BaseException: |
| 724 | + _logger.error(traceback.format_exc()) |
| 725 | + self.status = -1 |
| 726 | + continue |
| 727 | + |
| 728 | + return actual_outputs |
0 commit comments