Skip to content

Data Handlers

Glob patterns

File searches and image-name rules use wcmatch.glob with all compatible syntax features enabled: *, ?, character classes (including POSIX classes), braces, extglobs, numeric ranges, | alternatives, ! exclusions, tilde expansion, character escapes, hidden names, and recursive wildcards. Matching is case-sensitive, duplicate results are removed, and pattern expansion has no count limit.

Pattern Meaning
input/*.{tif,tiff} Both raster extensions
input/@(base\|scene_*).tif Extended alternatives within a filename
input/scene_<1-20>.tif Numeric range
input/*.tif\|!input/bad* TIFF files excluding names starting with bad
input/**/*.tif All depths, including hidden folders
input/***/*.tif All depths, also traversing directory symlinks
~/imagery/*.tif Files under the user's home directory

search_paths enables recursive wildcards by default; recursive=False treats ** and *** as ordinary *. A pattern without recursive wildcards searches only its specified levels. Folder inputs require default_file_pattern, which is evaluated relative to that folder, including alternatives and exclusions. Lists of input paths remain literal paths. Escape literal pattern characters with a backslash. Use / as a path separator on all platforms.

create_paths(template_pattern, paths_or_bases, *, default_file_pattern=None, debug_logs=False, replace_symbol='$', create_folders=True)

Create output paths using a filename template_pattern and a list of reference paths or names.

Parameters:

Name Type Description Default
template_pattern (str, required)

Defines output files from a glob path or folder to match input paths or names. Specify like: "/input/files/\(.tif" or "/input/folder" (while passing default_file_pattern like: '\).tif')

required
paths_or_bases List[str]

List of full paths or base names to derive the replace_symbol from.

required
default_file_pattern str

Used if template_pattern is a directory.

None
debug_logs bool

Whether to print the created paths.

False
replace_symbol str

Placeholder symbol in the template to replace with base names.

'$'
create_folders bool

Whether to create output folders if they don't exist.

True

Returns:

Type Description
List[str]

List[str]: List of constructed file paths.

Raises:

Type Description
ValueError

If template_pattern is a directory and default_file_pattern is not provided.

Source code in spectralmatch/handlers.py
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
def create_paths(
    template_pattern: str,
    paths_or_bases: List[str],
    *,
    default_file_pattern: str | None = None,
    debug_logs: bool = False,
    replace_symbol: str = "$",
    create_folders: bool = True,
) -> List[str]:
    """
    Create output paths using a filename template_pattern and a list of reference paths or names.

    Args:
        template_pattern (str, required): Defines output files from a glob path or folder to match input paths or names. Specify like: "/input/files/$.tif" or "/input/folder" (while passing default_file_pattern like: '$.tif')
        paths_or_bases (List[str]): List of full paths or base names to derive the replace_symbol from.
        default_file_pattern (str, optional): Used if `template_pattern` is a directory.
        debug_logs (bool): Whether to print the created paths.
        replace_symbol (str): Placeholder symbol in the template to replace with base names.
        create_folders (bool): Whether to create output folders if they don't exist.

    Returns:
        List[str]: List of constructed file paths.

    Raises:
        ValueError: If `template_pattern` is a directory and `default_file_pattern` is not provided.
    """
    if not os.path.basename(template_pattern).count("."):
        if not default_file_pattern:
            raise ValueError(
                "Template is a directory, but no default_file_pattern was provided."
            )
        template_pattern = os.path.join(template_pattern, default_file_pattern)

    output_paths = []
    for ref in paths_or_bases:
        base = (
            os.path.splitext(os.path.basename(ref))[0]
            if ("/" in ref or "\\" in ref)
            else os.path.splitext(ref)[0]
        )
        filename = template_pattern.replace(replace_symbol, base)
        output_paths.append(filename)

    if create_folders:
        for path in output_paths:
            os.makedirs(os.path.dirname(path), exist_ok=True)

    if debug_logs:
        print(f"Created {len(output_paths)} paths:")
        for p in output_paths:
            print(f"  {p}")

    return output_paths

match_paths(input_match_paths, reference_paths, match_regex, debug_logs=False)

Match reference_paths to input_match_paths using a regex applied to the basenames of input_match_paths. The extracted key must be a substring of the reference filename.

Parameters:

Name Type Description Default
input_match_paths List[str]

List of candidate paths to extract keys from.

required
reference_paths List[str]

List of reference paths to align to.

required
match_regex str

Regex applied to basenames of input_match_paths to extract a key to match via inclusion in reference_paths (e.g. "(.*)_LocalMatch.gpkg$" (without one of the backslashes)).

required
debug_logs bool

If True, print matched and unmatched file basenames.

False

Returns:

Type Description
List[Optional[str]]

List[Optional[str]]: A list the same length as reference_paths where each

List[Optional[str]]

element is the matched path from input_match_paths or None.

Raises:

Type Description
ValueError

If output list length does not match reference_paths length.

Source code in spectralmatch/handlers.py
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
def match_paths(
    input_match_paths: List[str],
    reference_paths: List[str],
    match_regex: str,
    debug_logs: bool = False,
) -> List[Optional[str]]:
    """
    Match `reference_paths` to `input_match_paths` using a regex applied to the basenames of `input_match_paths`. The extracted key must be a substring of the reference filename.

    Args:
        input_match_paths (List[str]): List of candidate paths to extract keys from.
        reference_paths (List[str]): List of reference paths to align to.
        match_regex (str): Regex applied to basenames of input_match_paths to extract a key to match via *inclusion* in reference_paths (e.g. "(.*)_LocalMatch\\.gpkg$" (without one of the backslashes)).
        debug_logs (bool): If True, print matched and unmatched file basenames.

    Returns:
        List[Optional[str]]: A list the same length as `reference_paths` where each
        element is the matched path from `input_match_paths` or None.

    Raises:
        ValueError: If output list length does not match reference_paths length.
    """
    pattern = re.compile(match_regex)
    match_keys = {}
    used_matches = set()

    # Extract keys from input_match_paths
    for mpath in input_match_paths:
        basename = os.path.basename(mpath)
        match = pattern.search(basename)
        if not match:
            continue
        key = match.group(1) if match.groups() else match.group(0)
        match_keys[key] = mpath

    # Match each reference path
    matched_list: List[Optional[str]] = []
    for rpath in reference_paths:
        rbase = os.path.basename(rpath)
        matched = None
        for key, mpath in match_keys.items():
            if key in rbase:
                matched = mpath
                used_matches.add(mpath)
                break
        matched_list.append(matched)

    # Validate output length
    if len(matched_list) != len(reference_paths):
        raise ValueError("Matched list length does not match reference_paths length.")

    return matched_list

search_paths(search_pattern, *, default_file_pattern=None, recursive=True, match_to_paths=None, debug_logs=False)

Search for files using wcmatch glob syntax, or a folder with a default file pattern.

Patterns support braces, extglobs, POSIX classes, numeric ranges, pipe-separated alternatives, ! exclusions, tilde expansion, character escapes, hidden names, ** recursion, and *** recursion through symlinks. Matching is case-sensitive and results are unique.

Parameters:

Name Type Description Default
search_pattern (str, required)

File path, wcmatch glob such as 'input//*.{tif,tiff}|!input//bad*', or folder searched with default_file_pattern; '/' works as a separator on all platforms.

required
default_file_pattern str

Wcmatch pattern relative to search_pattern when it is a folder, e.g. '.tif|.vrt'; required for folder inputs.

None
recursive bool

Interpret ** and *** as recursive wildcards (default True); False treats them as ordinary *, and patterns without them stay nonrecursive.

True
match_to_paths Tuple[List[str], str]

Matches input files to a reference list using a regex.

None
debug_logs bool

Whether to print matched paths.

False

Returns:

Type Description
List[str]

List[str]: Sorted list of matched file paths.

Raises:

Type Description
ValueError

If search_pattern is a directory and default_file_pattern is not provided.

Source code in spectralmatch/handlers.py
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
def search_paths(
    search_pattern: str,
    *,
    default_file_pattern: str | None = None,
    recursive: bool = True,
    match_to_paths: Tuple[List[str], str] | None = None,
    debug_logs: bool = False,
) -> List[str]:
    """
    Search for files using wcmatch glob syntax, or a folder with a default file pattern.

    Patterns support braces, extglobs, POSIX classes, numeric ranges, pipe-separated alternatives, ! exclusions, tilde expansion, character escapes, hidden names, ** recursion, and *** recursion through symlinks. Matching is case-sensitive and results are unique.

    Args:
        search_pattern (str, required): File path, wcmatch glob such as 'input/**/*.{tif,tiff}|!input/**/bad*', or folder searched with default_file_pattern; '/' works as a separator on all platforms.
        default_file_pattern (str, optional): Wcmatch pattern relative to search_pattern when it is a folder, e.g. '*.tif|*.vrt'; required for folder inputs.
        recursive (bool, optional): Interpret ** and *** as recursive wildcards (default True); False treats them as ordinary *, and patterns without them stay nonrecursive.
        match_to_paths (Tuple[List[str], str], optional): Matches input files to a reference list using a regex.
        debug_logs (bool, optional): Whether to print matched paths.

    Returns:
        List[str]: Sorted list of matched file paths.

    Raises:
        ValueError: If `search_pattern` is a directory and `default_file_pattern` is not provided.
    """
    _print_step_start("search_paths")
    root_dir = None
    folder = os.path.expanduser(search_pattern)
    if os.path.isdir(folder) or folder.endswith(os.sep):
        if not default_file_pattern:
            raise ValueError(
                "Pattern is a directory, but no default_file_pattern was provided."
            )
        root_dir = folder
        search_pattern = default_file_pattern

    display_pattern = os.path.join(root_dir, search_pattern) if root_dir is not None else search_pattern
    print(f"Searching for glob matches: {display_pattern}", flush=True)
    flags = GLOB_FLAGS | glob.NODIR
    if not recursive:
        flags &= ~(glob.GLOBSTAR | glob.GLOBSTARLONG)
    input_paths = glob.glob(search_pattern, flags=flags, root_dir=root_dir, limit=0)
    if root_dir is not None:
        input_paths = [os.path.join(root_dir, path) for path in input_paths]
    input_paths.sort()

    if debug_logs:
        print(f"Found {len(input_paths)} file(s) matching: {display_pattern}")

    if match_to_paths:
        input_paths = match_paths(input_paths, *match_to_paths)

    return input_paths