4848 use_tqdm = False
4949
5050
51- def check_input_format (input ):
51+ def check_input_format (input , allow_flat = False ):
5252 p_input = Path (input )
5353 if not p_input .exists ():
5454 err_msg = f'The provided input folder "{ input } " does not exists.'
@@ -60,14 +60,19 @@ def check_input_format(input):
6060 raise ValueError (f'The provided input folder "{ input } " is not a directory' )
6161
6262 dirs = list_dirs (input )
63- if len (dirs ) == 0 :
63+ if len (dirs ) == 0 and not allow_flat :
6464 raise ValueError (
6565 f'The input data is not in a right format. Within your folder "{ input } "'
6666 " there are no directories. Consult the documentation how to the folder"
6767 " structure should look like."
6868 )
6969
7070
71+ def _is_flat (input ):
72+ """Returns True if the input directory has no subdirectories (flat file layout)."""
73+ return len (list_dirs (input )) == 0
74+
75+
7176def _get_copy_fn (move ):
7277 """Return a function(src, dst_dir) that copies/moves/symlinks a file into dst_dir."""
7378 if move is True or move == "move" :
@@ -114,7 +119,7 @@ def ratio(
114119 if len (ratio ) not in (2 , 3 ):
115120 raise ValueError ("`ratio` should" )
116121
117- check_input_format (input )
122+ check_input_format (input , allow_flat = ( group != "sibling" ) )
118123 valid_extensions (formats )
119124
120125 if use_tqdm :
@@ -124,6 +129,11 @@ def ratio(
124129 split_sibling_dirs_ratio (
125130 input , output , ratio , seed , prog_bar if use_tqdm else None , move , formats , shuffle ,
126131 )
132+ elif _is_flat (input ):
133+ split_flat_dir_ratio (
134+ input , output , ratio , seed , prog_bar if use_tqdm else None ,
135+ group_prefix , group , move , formats , shuffle ,
136+ )
127137 else :
128138 for class_dir in list_dirs (input ):
129139 split_class_dir_ratio (
@@ -139,12 +149,17 @@ def fixed(
139149 input , output = "output" , seed = 1337 , fixed = (100 , 100 ), oversample = False ,
140150 group_prefix = None , group = None , move = False , formats = None , shuffle = True ,
141151):
142- check_input_format (input )
152+ check_input_format (input , allow_flat = ( group != "sibling" ) )
143153 valid_extensions (formats )
144154
145155 if group == "sibling" and oversample :
146156 raise ValueError ("Cannot use `oversample=True` with `group='sibling'` (no classes to balance)." )
147157
158+ flat = _is_flat (input ) and group != "sibling"
159+
160+ if flat and oversample :
161+ raise ValueError ("Cannot use `oversample=True` with a flat input directory (no classes to balance)." )
162+
148163 if fixed == "auto" :
149164 if not oversample :
150165 raise ValueError (
@@ -180,6 +195,15 @@ def fixed(
180195 prog_bar .close ()
181196 return
182197
198+ if flat :
199+ split_flat_dir_fixed (
200+ input , output , fixed , seed , prog_bar if use_tqdm else None ,
201+ group_prefix , group , move , formats , shuffle ,
202+ )
203+ if use_tqdm :
204+ prog_bar .close ()
205+ return
206+
183207 classes_dirs = list_dirs (input )
184208 num_items = []
185209 for class_dir in classes_dirs :
@@ -235,7 +259,7 @@ def kfold(
235259 if k < 2 :
236260 raise ValueError ("`k` must be 2 or greater." )
237261
238- check_input_format (input )
262+ check_input_format (input , allow_flat = ( group != "sibling" ) )
239263 valid_extensions (formats )
240264
241265 if use_tqdm :
@@ -245,6 +269,11 @@ def kfold(
245269 split_sibling_dirs_kfold (
246270 input , output , k , seed , prog_bar if use_tqdm else None , move , formats , shuffle ,
247271 )
272+ elif _is_flat (input ):
273+ split_flat_dir_kfold (
274+ input , output , k , seed , prog_bar if use_tqdm else None ,
275+ group_prefix , group , move , formats , shuffle ,
276+ )
248277 else :
249278 for class_dir in list_dirs (input ):
250279 split_class_dir_kfold (
@@ -384,6 +413,85 @@ def copy_files(files_type, class_dir, output, prog_bar, move):
384413 copy_fn (f , full_path )
385414
386415
416+ def copy_files_flat (files_type , output , prog_bar , move ):
417+ """
418+ Copies files into output/split_name/ without class subdirectories.
419+ """
420+ copy_fn = _get_copy_fn (move )
421+
422+ for files , folder_type in files_type :
423+ full_path = Path (output ) / folder_type
424+
425+ full_path .mkdir (parents = True , exist_ok = True )
426+ for f in files :
427+ if prog_bar is not None :
428+ prog_bar .update ()
429+ if isinstance (f , tuple ):
430+ for x in f :
431+ copy_fn (x , full_path )
432+ else :
433+ copy_fn (f , full_path )
434+
435+
436+ def split_flat_dir_ratio (input_dir , output , ratio , seed , prog_bar , group_prefix , group , move , formats , shuffle = True ):
437+ """Splits a flat directory (no class subdirs)."""
438+ files = setup_files (input_dir , seed , group_prefix , group , formats , shuffle )
439+
440+ split_train_idx = int (ratio [0 ] * len (files ))
441+ split_val_idx = split_train_idx + int (ratio [1 ] * len (files ))
442+
443+ li = split_files (files , split_train_idx , split_val_idx , len (ratio ) == 3 )
444+ copy_files_flat (li , output , prog_bar , move )
445+
446+
447+ def split_flat_dir_fixed (input_dir , output , fixed , seed , prog_bar , group_prefix , group , move , formats , shuffle = True ):
448+ """Splits a flat directory with fixed counts."""
449+ files = setup_files (input_dir , seed , group_prefix , group , formats , shuffle )
450+
451+ if not len (files ) >= sum (fixed ):
452+ raise ValueError (
453+ f"Not enough files for the requested fixed split."
454+ f" There are only { len (files )} files but fixed={ fixed } requires at least { sum (fixed )} ."
455+ )
456+
457+ if len (fixed ) <= 2 :
458+ split_train_idx = len (files ) - sum (fixed )
459+ split_val_idx = split_train_idx + fixed [0 ]
460+ else :
461+ split_train_idx = fixed [0 ]
462+ split_val_idx = fixed [0 ] + fixed [1 ]
463+
464+ li = split_files (
465+ files ,
466+ split_train_idx ,
467+ split_val_idx ,
468+ len (fixed ) >= 2 ,
469+ None if len (fixed ) != 3 else fixed [2 ],
470+ )
471+ copy_files_flat (li , output , prog_bar , move )
472+
473+
474+ def split_flat_dir_kfold (input_dir , output , k , seed , prog_bar , group_prefix , group , move , formats , shuffle = True ):
475+ """Splits a flat directory into k folds."""
476+ files = setup_files (input_dir , seed , group_prefix , group , formats , shuffle )
477+
478+ fold_size = len (files ) // k
479+ remainder = len (files ) % k
480+ partitions = []
481+ idx = 0
482+ for i in range (k ):
483+ size = fold_size + (1 if i < remainder else 0 )
484+ partitions .append (files [idx : idx + size ])
485+ idx += size
486+
487+ for i in range (k ):
488+ val_files = partitions [i ]
489+ train_files = [f for j , part in enumerate (partitions ) if j != i for f in part ]
490+ fold_output = str (Path (output ) / f"fold_{ i + 1 } " )
491+ li = [(train_files , "train" ), (val_files , "val" )]
492+ copy_files_flat (li , fold_output , prog_bar , move )
493+
494+
387495def copy_sibling_files (files_type , type_dir_names , output , prog_bar , move ):
388496 """
389497 Copies files in sibling mode: each group is a tuple of files across type dirs.
0 commit comments