@@ -190,6 +190,8 @@ def write_session(self, subject:Subject, session:Session, on_conflict=OnConflict
190190 self .write_solution_ids (session , [])
191191 if not self .get_runs_filename (subject .id , session_id ).exists ():
192192 self .write_run_ids (session .subject_id , session .id , [])
193+ if not self .get_photocollections_filename (subject .id , session_id ).exists ():
194+ self .write_reference_numbers (session .subject_id , session .id , [])
193195 if not self .get_photoscans_filename (subject .id , session_id ).exists ():
194196 self .write_photoscan_ids (session .subject_id , session .id , [])
195197
@@ -396,6 +398,42 @@ def write_volume(self, subject_id, volume_id, volume_name, volume_data_filepath,
396398
397399 self .logger .info (f"Added volume with ID { volume_id } for subject { subject_id } to the database." )
398400
401+ def write_photocollection (self , subject_id , session_id , reference_number : str , photo_paths : List [PathLike ], on_conflict = OnConflictOpts .ERROR ):
402+ """ Writes a photocollection to database and copies the associated
403+ photos into the database, specified by the subject, session, and
404+ reference_number of the photocollection."""
405+
406+ photocollection_dir = Path (self .get_session_dir (subject_id , session_id )) / 'photocollections' / reference_number
407+
408+ reference_numbers = self .get_photocollection_reference_numbers (subject_id , session_id )
409+ if reference_number in reference_numbers :
410+ if on_conflict == OnConflictOpts .ERROR :
411+ raise ValueError (f"Photocollection with reference number { reference_number } already exists for session { session_id } ." )
412+ elif on_conflict == OnConflictOpts .OVERWRITE :
413+ self .logger .info (f"Overwriting photocollection with reference number { reference_number } for session { session_id } ." )
414+ if photocollection_dir .exists ():
415+ shutil .rmtree (photocollection_dir )
416+ elif on_conflict == OnConflictOpts .SKIP :
417+ self .logger .info (f"Skipping photocollection with reference number { reference_number } for session { session_id } as it already exists." )
418+ return
419+ else :
420+ raise ValueError ("Invalid 'on_conflict' option. Use 'error', 'overwrite', or 'skip'." )
421+
422+ photocollection_dir .mkdir (exist_ok = True )
423+
424+ # Copy each photo into the photocollection directory
425+ for photo_path in photo_paths :
426+ photo_path = Path (photo_path )
427+ if not photo_path .exists ():
428+ raise FileNotFoundError (f"Photo file does not exist: { photo_path } " )
429+ shutil .copy (photo_path , photocollection_dir )
430+
431+ if reference_number not in reference_numbers :
432+ reference_numbers .append (reference_number )
433+ self .write_reference_numbers (subject_id ,session_id , reference_numbers )
434+
435+ self .logger .info (f"Added photocollection with reference number { reference_number } for session { session_id } to the database." )
436+
399437 def write_photoscan (self , subject_id , session_id , photoscan : Photoscan , model_data_filepath : str | None = None , texture_data_filepath : str | None = None , mtl_data_filepath : str | None = None , on_conflict = OnConflictOpts .ERROR ):
400438 """ Writes a photoscan object to database and copies the associated model and texture data filepaths that are required for generating a photoscan into the database.
401439 .mtl files are not required for generating a photoscan but can be provided if present.
@@ -600,6 +638,16 @@ def get_solution_ids(self, subject_id:str, session_id:str) -> List[str]:
600638
601639 return json .loads (solutions_filename .read_text ())["solution_ids" ]
602640
641+ def get_photocollection_reference_numbers (self , subject_id : str , session_id : str ) -> List [str ]:
642+ """Get a list of reference numbers of the photocollections associated with the given session"""
643+ photocollection_filename = self .get_photocollections_filename (subject_id , session_id )
644+
645+ if not (photocollection_filename .exists () and photocollection_filename .is_file ()):
646+ self .logger .warning ("Photocollection file not found for subject %s, session %s." , subject_id , session_id )
647+ return []
648+
649+ return json .loads (photocollection_filename .read_text ())["reference_numbers" ]
650+
603651 def get_photoscan_ids (self , subject_id : str , session_id : str ) -> List [str ]:
604652 """Get a list of IDs of the photoscans associated with the given session"""
605653 photoscan_filename = self .get_photoscans_filename (subject_id , session_id )
@@ -685,6 +733,31 @@ def get_volume_info(self, subject_id, volume_id):
685733 "name" : volume ["name" ],\
686734 "data_abspath" : Path (volume_metadata_filepath ).parent / volume ["data_filename" ]}
687735
736+ def get_photocollection_absolute_filepaths (self , subject_id : str , session_id : str , reference_number : str ) -> List [Path ]:
737+ """
738+ get the absolute filepaths of all photos in a specific photocollection.
739+
740+ Args:
741+ subject_id (str): The subject ID.
742+ session_id (str): The session ID.
743+ reference_number (str): The reference number of the photocollection.
744+
745+ Returns:
746+ List[Path]: List of absolute file paths to the photos in the photocollection.
747+ """
748+ photocollection_dir = (
749+ Path (self .get_session_dir (subject_id , session_id )) / 'photocollections' / reference_number
750+ )
751+
752+ if not photocollection_dir .exists () or not photocollection_dir .is_dir ():
753+ self .logger .warning (
754+ f"Photocollection directory not found for subject { subject_id } , "
755+ f"session { session_id } , photocollection { reference_number } ."
756+ )
757+ return []
758+
759+ return sorted (photocollection_dir .glob ("*" ))
760+
688761 def get_photoscan_absolute_filepaths_info (self , subject_id , session_id , photoscan_id ):
689762 """Returns the photoscan information with absolute paths to any data"""
690763 photoscan_metadata_filepath = self .get_photoscan_metadata_filepath (subject_id , session_id , photoscan_id )
@@ -949,6 +1022,11 @@ def get_solutions_filename(self, subject_id, session_id) -> Path:
9491022 session_dir = self .get_session_dir (subject_id , session_id )
9501023 return Path (session_dir ) / 'solutions' / 'solutions.json'
9511024
1025+ def get_photocollections_filename (self , subject_id , session_id ) -> Path :
1026+ """Get the path to the overall photocollections json file for the requested session"""
1027+ session_dir = self .get_session_dir (subject_id , session_id )
1028+ return Path (session_dir ) / 'photocollections' / 'photocollections.json'
1029+
9521030 def get_photoscans_filename (self , subject_id , session_id ) -> Path :
9531031 """Get the path to the overall photoscans json file for the requested session"""
9541032 session_dir = self .get_session_dir (subject_id , session_id )
@@ -1045,6 +1123,13 @@ def write_transducer_ids(self, transducer_ids):
10451123 with open (transducers_filename , 'w' ) as f :
10461124 json .dump (transducers_data , f )
10471125
1126+ def write_reference_numbers (self , subject_id , session_id , reference_numbers : List [str ]):
1127+ photocollection_data = {'reference_numbers' : reference_numbers }
1128+ photocollection_filename = self .get_photocollections_filename (subject_id , session_id )
1129+ photocollection_filename .parent .mkdir (exist_ok = True ) # Make a photocollection directory in case it does not exist
1130+ with open (photocollection_filename , 'w' ) as f :
1131+ json .dump (photocollection_data ,f )
1132+
10481133 def write_photoscan_ids (self , subject_id , session_id , photoscan_ids : List [str ]):
10491134 photoscan_data = {'photoscan_ids' : photoscan_ids }
10501135 photoscan_filename = self .get_photoscans_filename (subject_id , session_id )
0 commit comments