4242
4343PROJECT_FILENAME = "project.qpproj"
4444ANNOTATIONS_BATCH_SIZE = 500000
45+ JSON_SUFFIX = ".json"
4546
4647
4748class QuPathVersion (BaseModel ):
@@ -1124,40 +1125,46 @@ def add(
11241125 progress_callable (progress )
11251126
11261127 # We communicate via file I/O with the Groovy script running within QuPath
1127- with (
1128- tempfile .NamedTemporaryFile (mode = "w" , suffix = ".json" , encoding = "utf-8" ) as paths_file ,
1129- tempfile .NamedTemporaryFile (mode = "w" , suffix = ".json" , encoding = "utf-8" ) as output_file ,
1130- ):
1131- try :
1132- json .dump ([str (path .resolve ()) for path in paths ], paths_file .file )
1133- paths_file .flush ()
1128+ # Use delete=False to avoid Windows file locking issues - the file must be closed
1129+ # before QuPath can read it, but NamedTemporaryFile keeps files locked while open on Windows
1130+ paths_file = tempfile .NamedTemporaryFile (mode = "w" , suffix = JSON_SUFFIX , encoding = "utf-8" , delete = False ) # noqa: SIM115
1131+ output_file = tempfile .NamedTemporaryFile (mode = "w" , suffix = JSON_SUFFIX , encoding = "utf-8" , delete = False ) # noqa: SIM115
1132+ try :
1133+ # Write paths and close file so QuPath can read it
1134+ json .dump ([str (path .resolve ()) for path in paths ], paths_file )
1135+ paths_file .close ()
1136+ output_file .close ()
11341137
1135- pid = Service .execute_qupath (
1136- script = Service ._find_groovy_script ("add" ),
1137- script_args = [str (project ), str ( paths_file .name ), str ( output_file .name ) ],
1138- )
1138+ pid = Service .execute_qupath (
1139+ script = Service ._find_groovy_script ("add" ),
1140+ script_args = [str (project ), paths_file .name , output_file .name ],
1141+ )
11391142
1140- if not pid :
1141- message = "Failed to execute QuPath script for adding images."
1142- logger .error (message )
1143- raise RuntimeError (message ) # noqa: TRY301
1144-
1145- with Path (output_file .name ).open ("r" , encoding = "utf-8" ) as f :
1146- result_data = json .load (f )
1147- added_count = int (result_data .get ("added_count" , 0 ))
1148- errors = result_data .get ("errors" , [])
1149- for error in errors :
1150- logger .warning (f"QuPath add script error: { error } " )
1151-
1152- if progress_callable :
1153- progress .status = AddProgressState .COMPLETED
1154- progress_callable (progress )
1155-
1156- return added_count
1157- except Exception as e :
1158- message = f"Failed to add images to QuPath project: { e !s} "
1159- logger .exception (message )
1160- raise RuntimeError (message ) from e
1143+ if not pid :
1144+ message = "Failed to execute QuPath script for adding images."
1145+ logger .error (message )
1146+ raise RuntimeError (message ) # noqa: TRY301
1147+
1148+ with Path (output_file .name ).open ("r" , encoding = "utf-8" ) as f :
1149+ result_data = json .load (f )
1150+ added_count = int (result_data .get ("added_count" , 0 ))
1151+ errors = result_data .get ("errors" , [])
1152+ for error in errors :
1153+ logger .warning (f"QuPath add script error: { error } " )
1154+
1155+ if progress_callable :
1156+ progress .status = AddProgressState .COMPLETED
1157+ progress_callable (progress )
1158+
1159+ return added_count
1160+ except Exception as e :
1161+ message = f"Failed to add images to QuPath project: { e !s} "
1162+ logger .exception (message )
1163+ raise RuntimeError (message ) from e
1164+ finally :
1165+ # Clean up temp files
1166+ Path (paths_file .name ).unlink (missing_ok = True )
1167+ Path (output_file .name ).unlink (missing_ok = True )
11611168
11621169 @staticmethod
11631170 def annotate (
@@ -1246,9 +1253,12 @@ def inspect(project: Path) -> QuPathProject:
12461253 RuntimeError: If there is an error inspecting the project.
12471254 """
12481255 # We communicate via file I/O with the Groovy script running within QuPath
1249- with tempfile .NamedTemporaryFile (mode = "w+" , suffix = ".json" , encoding = "utf-8" ) as temp_file :
1250- output_file = Path (temp_file .name ).resolve ()
1251-
1256+ # Use delete=False to avoid Windows file locking issues - the file must be closed
1257+ # before QuPath can write to it, but NamedTemporaryFile keeps files locked while open on Windows
1258+ temp_file = tempfile .NamedTemporaryFile (mode = "w+" , suffix = JSON_SUFFIX , encoding = "utf-8" , delete = False ) # noqa: SIM115
1259+ output_file = Path (temp_file .name ).resolve ()
1260+ temp_file .close ()
1261+ try :
12521262 pid = Service .execute_qupath (
12531263 quiet = True ,
12541264 project = project ,
@@ -1267,3 +1277,5 @@ def inspect(project: Path) -> QuPathProject:
12671277 raise RuntimeError (message )
12681278
12691279 return QuPathProject .model_validate_json (output_file .read_text (encoding = "utf-8" ))
1280+ finally :
1281+ output_file .unlink (missing_ok = True )
0 commit comments