Is there a way to extract annotation information?

I’d like to extract the annotation information from some files into text or excel. I have captures using two (or more) scopes and at least dozens of annotated regions per file. I’d like to be able to get the annotated information from both of the scopes.
I looked around in the python, but nothing jumped out at me as a way to get the info. It looks like the annotation files are not human readable, but maybe it’s possible to extract or dump the information from those somehow?

Hi @Jeremy - The annotation files are JLS files, too. It’s actually pretty easy to get the annotation information. I created this example:

I right-clicked and selected Export visible data to annotation_example.jls. The UI creates both annotation_example.jls and annotation_example.anno.jls.

You can then use pyjls.Reader to open the JLS file and the annotations method to invoke a callback for each annotation. Here is an example that generates a list of the annotation dicts and then prints each annotation dict:

# Copyright 2025 Jetperch LLC
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from pyjls import Reader, SignalType, AnnotationType
import argparse


def get_parser():
    p = argparse.ArgumentParser(
        description='Extract annotations from a JLS file.')
    p.add_argument('src',
                   help='The source JLS file')
    p.add_argument('--signal', '--signal_id',
                   default=1,
                   help='The signal name or id to export.')
    return p


ANNOTATION_MAP = {
    AnnotationType.USER: 'user',
    AnnotationType.TEXT: 'text',
    AnnotationType.VMARKER: 'vmarker',
    AnnotationType.HMARKER: 'hmarker',
}


def run():
    args = get_parser().parse_args()
    
        
    with Reader(args.src) as r:
        signal = r.signal_lookup(args.signal)
        if signal.signal_type != SignalType.FSR:
            print('Signal is not FSR')
            return 1
        
        annotations = []
        def annotation_callback_fn(sample_id, y, annotation_type, group_id, data):
            annotations.append({
                'sample_id': sample_id,
                'y': y,
                'annotation_type': annotation_type,
                'annotation_type_str': ANNOTATION_MAP[annotation_type],
                'group_id': group_id,
                'data': data,
            })
        r.annotations(signal.signal_id, 0, annotation_callback_fn)
        for annotation in annotations:
            annotation['utc'] = r.sample_id_to_timestamp(signal.signal_id, annotation['sample_id'])
            print(annotation)
        
    return 0


if __name__ == '__main__':
    run()

The underlying C contains the best documentation on the annotation format:

The Joulescope UI always stores vertical markers on signal_id 1. Horizontal markers and text markers are stored on their appropriate signal. You can use:

python -m pyjls info {filename.jls}

to display the signals in a JLS file.

For markers, the Joulescope UI sets data to an identifier with \x1c (file separator) optionally separating a JSON data structure with UI information. The UI uses a naming convention of {integer} for single markers and {integer}{a|b} for dual markers.

Does this make sense and give you something close enough to what you need?

Hi Matt,
Thanks for the code and documentation links!
Based on testing it with a simple file, it looks like this can access information about the sample numbers and timestamps. I didn’t see any voltage or current info in what was printed nor in the proximate info in the documentation links, so I think I need to open the data file and use the sample (or maybe time, but that’s probably harder) and process that to get the signal statistics for the particular annotation.
I’ll give that a shot tomorrow if there’s no preferred approaches raised.

Hi @Jeremy - The annotations are just time locations. To be safe, you should go through UTC to compare signals. The methods are pretty simple:

utc = r.sample_id_to_timestamp(signal_id, sample_id)
sample_id = r.timestamp_to_sample_id(signal_id, utc)

You can then get the statistics for dual markers using Reader.fsr_statistics, like this:

increment = sample_id_end - sample_id_start + 1
statistics = r.fsr_statistics(signal_id, sample_id_start, increment, 1)

You will need to open the data JLS file provide the desired signal_id corresponding to either current or voltage. The signal_id’s are not fixed and can vary. You can use signal_lookup to get the signal. Provide current, voltage, or power. If you have multiple Joulescopes, you also need to provide the source spec, like this: JS220-002557.current. Use signal.signal_id to provide the signal_id to fsr_statistics.