Adding a photo of an activity

1,276 views
Skip to first unread message

Jérôme launay

unread,
Jul 26, 2021, 5:33:08 PM7/26/21
to golden-cheetah-users
Hi,
Is there a way to add a photo to an activity (eg to remember a place on the course)?
If this feature does not exist, are there any plans to add it?
Regards,
Jérôme

Ale Martinez

unread,
Jul 27, 2021, 7:13:30 AM7/27/21
to golden-cheetah-users
El lunes, 26 de julio de 2021 a la(s) 18:33:08 UTC-3, launayj...@gmail.com escribió:
Hi,
Is there a way to add a photo to an activity (eg to remember a place on the course)?
 
No 

If this feature does not exist, are there any plans to add it?

Not that I know  

Jérôme launay

unread,
Jul 27, 2021, 12:54:57 PM7/27/21
to golden-cheetah-users
Ok, thanks for your answer. Too bad :(

Mark Liversedge

unread,
Jul 29, 2021, 5:57:36 AM7/29/21
to golden-cheetah-users
I think its a fairly trivial feature to add, we could create a gallery tile on the overview, drag and drop photos into it and they are associated with the ride and let you view them there.
Might have a go at that, since honestly, its about time we supported it and could be genuinely useful for folks doing field testing- to see bike positions etc.

Jérôme launay

unread,
Jul 29, 2021, 7:39:35 AM7/29/21
to golden-cheetah-users
Mark, it would be really great. I often take pictures to remember a ride and I miss this feature.
Looking forward to see this.
Thanks a lot for the work on GC

James

unread,
Aug 11, 2022, 6:09:57 AM8/11/22
to golden-cheetah-users
Just adding my voice to say I'd love this feature too. Not being performance focused my usage more like a fitness journal so photos would really enhance that.

Jérôme launay

unread,
Aug 19, 2022, 7:27:44 AM8/19/22
to golden-cheetah-users
I opened a feature request here 6 months ago, no reaction on this topic :/
I'm still hoping it will happen someday as Mark said its a "fairly trivial feature".

Ale Martinez

unread,
Aug 19, 2022, 3:06:39 PM8/19/22
to golden-cheetah-users
El viernes, 19 de agosto de 2022 a la(s) 08:27:44 UTC-3, launayj...@gmail.com escribió:
I opened a feature request here 6 months ago, no reaction on this topic :/

What kind of feedback you were expecting, beside the one you received in this thread? 

Jérôme launay

unread,
Aug 19, 2022, 4:10:54 PM8/19/22
to golden-cheetah-users
I didn't expect anything, I just opened this topic because I thought it was the right place to reach the most people because not everyone uses google groups.

Ale Martinez

unread,
Aug 19, 2022, 7:12:32 PM8/19/22
to golden-cheetah-users
El viernes, 19 de agosto de 2022 a la(s) 17:10:54 UTC-3, launayj...@gmail.com escribió:
I didn't expect anything

The “no reaction on this topic :/” remark induced me to think otherwise. 

Jérôme launay

unread,
Aug 19, 2022, 7:34:37 PM8/19/22
to golden-cheetah-users
My English is basic, I write this because it is, that's all :)
Going back to the subject of the thread, it would be really great to see this feature added, it's the only thing I really miss in GC.

Ale Martinez

unread,
Aug 20, 2022, 12:22:04 PM8/20/22
to golden-cheetah-users
El viernes, 19 de agosto de 2022 a la(s) 20:34:37 UTC-3, launayj...@gmail.com escribió:
My English is basic, I write this because it is, that's all :)
Going back to the subject of the thread, it would be really great to see this feature added, it's the only thing I really miss in GC.

It looks like this feature is very important to you, but perhaps being ironic while repeatedly asking for it may be not the best strategy to get it delivered.

Jérôme launay

unread,
Aug 20, 2022, 5:01:08 PM8/20/22
to golden-cheetah-users
I've been looking for it, but I don't see where I've been ironic or  even why you say I ask repeatedly.
Sorry if it annoys you because it is not the goal.

Medien Forscher

unread,
Jan 19, 2024, 2:25:30 PMJan 19
to golden-cheetah-users
Hi, I also would love the feature. Is this planned? Thx for considering.

Ale Martinez

unread,
Jan 19, 2024, 4:59:20 PMJan 19
to golden-cheetah-users
El viernes, 19 de enero de 2024 a la(s) 4:25:30 p.m. UTC-3, Medien Forscher escribió:
Hi, I also would love the feature. Is this planned?

IIRC it is included in Aerolab 2 feature request, you can check planned features in https://github.com/GoldenCheetah/GoldenCheetah/issues?q=is%3Aopen+is%3Aissue+milestone%3A3.7, if not there is not likely to be included.

BTW, I think this thread and similar ones seems to expose a misunderstanding about how new features can be added, IMHO.

The 1st mechanism is a developer considers the requested feature worth enough to dedicate free time to development, testing and deployment. Many GoldenCheetah features has gone this route, but it is not the only one and, more important, no user is entitled for this, but this is not the only mechanism.

The 2nd one is for the user to dedicate their free time to learn how to implement the feature and contribute it to the project, many features has been added to GoldenCheetah this way to, but what happens if you are not a developer, are not willing to learn how to be one or, prefer to dedicate your free time to more rewarding endeavors.

There is a 3rd mechanism: you can hire a developer to do the work and then, ideally, contribute it to the project to give back something for all you have received for free.

Problem happens when users become fixated with the 1st option and, no mater how persuading you think you are, how important is this for you, or even or others you choose to advocate for, you cannot enforce this, more than that, insistence may have the opposite effect, at least it is so in my case, I am too old and sick to have a boss again.

Hope it helps, Ale.


Medien Forscher

unread,
Jan 20, 2024, 4:28:01 AMJan 20
to golden-cheetah-users
Thank you for your detailed explanation. I'm sorry that my question had a demanding tone.

I tried a workaround with a tab website where you can also point to local files. But this is not activity specific, the same foto is shown in all activities.

Have a good day
Best, Olivier

Ale Martinez

unread,
Jan 20, 2024, 5:21:55 AMJan 20
to golden-cheetah-users
El sábado, 20 de enero de 2024 a la(s) 6:28:01 a.m. UTC-3, Medien Forscher escribió:
Thank you for your detailed explanation. I'm sorry that my question had a demanding tone. 

Don’t worry, it is something I think it needs to be said, but it is not necessarily specific to your question.
 
I tried a workaround with a tab website where you can also point to local files. But this is not activity specific, the same foto is shown in all activities. 

That approach could work, but using a Python Chart instead of a web chart: it can also show a local file as a webpage, but determined by a Python script based on activity metadata. 

Medien Forscher

unread,
Jan 20, 2024, 12:30:51 PMJan 20
to golden-cheetah-users
Thx for this idea. I'll look into it and post again if I find the solution. 
Message has been deleted
Message has been deleted

Medien Forscher

unread,
Jan 21, 2024, 11:26:06 AMJan 21
to golden-cheetah-users
Here's a phyton script for a chart that displays images. Instructions in the comment section. (updated version with image grid)

##Activity Images by Olivier 01-2024


##There must be a details tab "Images" with text fields named "Image_1" to "Image_10"

##Enter the file path to the images in the text fields (windows: shift rightclick on image, copy path)


colc=0

colnr=0

# Save paths to variables

activity = GC.activity()

img1 = GC.activityMetrics()['Image_1']

img2 = GC.activityMetrics()['Image_2']

img3 = GC.activityMetrics()['Image_3']

img4 = GC.activityMetrics()['Image_4']

img5 = GC.activityMetrics()['Image_5']

img6 = GC.activityMetrics()['Image_6']

img7 = GC.activityMetrics()['Image_7']

img8 = GC.activityMetrics()['Image_8']

img9 = GC.activityMetrics()['Image_9']

img10 = GC.activityMetrics()['Image_10']


# check if paths are given. If not the html img src is empty

if img1 != "":

   imgt1 = """<img src=""" +img1+""" max-width: 60%>"""

   colc=colc+1

else:

   imgt1=""

if img2 != "":

   imgt2 = """<img src=""" +img2+""" max-width: 60%>"""

   colc=colc+1

else:

   imgt2=""

if img3 != "":

   imgt3 = """<img src=""" +img3+""" max-width: 100%>"""

   colc=colc+1

else:

   imgt3=""

if img4 != "":

   imgt4 = """<img src=""" +img4+""" max-width: 100%>"""

   colc=colc+1

else:

   imgt4=""

if img5 != "":

   imgt5 = """<img src=""" +img5+""" max-width: 100%>"""

   colc=colc+1

else:

   imgt5=""

if img6 != "":

   imgt6 = """<img src=""" +img6+""" max-width: 100%>"""

   colc=colc+1

else:

   imgt6=""

if img7 != "":

   imgt7 = """<img src=""" +img7+""" max-width: 100%>"""

   colc=colc+1

else:

   imgt7=""

if img8 != "":

   imgt8 = """<img src=""" +img8+""" max-width: 100%>"""

   colc=colc+1

else:

   imgt8=""

if img9 != "":

   imgt9 = """<img src=""" +img9+""" max-width: 100%>"""

   colc=colc+1

else:

   imgt9=""

if img10 != "":

   imgt10 = """<img src=""" +img10+""" max-width: 100%>"""

   colc=colc+1

else:

   imgt10=""


# Count Nr of Images to dynamically adjust colums

if colc < 3:

   colnr=1

if colc > 2 and colc < 7:

   colnr=2

if colc > 6:

   colnr=3


# create a temporary html file and fill it

import tempfile

tmp=tempfile.NamedTemporaryFile(delete=False)

path=tmp.name+'.html'


f=open(path, 'w')

txt=f"""

<html>

<head>

<style>

div {{

max-width: 1200px;

margin: 0 auto;

}}


#photos {{

line-height: 0;

-webkit-column-count: {colnr};

-webkit-column-gap: 10px;

-moz-column-count: {colnr};

-moz-column-gap: 10px;

column-count: {colnr};

column-gap: 10px;

}}


#photos img {{

width: 100%;

height: auto !important;

margin-bottom: 10px;

}}


@media (max-width: 1200px) {{

#photos {{

column-count: {colnr};

}}

}}


@media (max-width: 980px) {{

#photos {{

column-count: {colnr};

}}

}}


@media (max-width: 640px) {{

#photos {{

column-count: {colnr};

}}

}}


</style>

</head>

<body style="background-color:black;">

<div>

<section id="photos">

{imgt1}

{imgt2}

{imgt3}

{imgt4}

{imgt5}

{imgt6}

{imgt7}

{imgt8}

{imgt9}

{imgt10}

</section>

</div>


</body>

</html>

"""

# write the html file to the GC.webpage

f.write(txt)

f.close()

GC.webpage('file://' + path)

print("no errors")


Message has been deleted

Manuel Oberti

unread,
Jan 21, 2024, 1:47:52 PMJan 21
to golden-cheetah-users


533
/1500
Trad

In fact Medien there would be a fourth solution.....pass to intervals.icu.

Mr. @Ale Martinez is rude ,opinionated and his only a response is always....read the wiki......,

he does not appreciate those who are not able to program like him.

Leave GC as I did ( because of Martinez ) and switch to intervals.icu, which you can browse online from any device, the founder is available to requests from EVERYONE and the community is rich in insights

GC is free only on paper....in reality he makes changes only if they interest him

Jérôme launay

unread,
Jan 25, 2024, 8:50:52 PMJan 25
to golden-cheetah-users

Thanks a lot Medien,
I hadn’t thought at all about using a Python chart; that’s a great idea.
I modified your script so that there’s no need to create text fields or copy/paste the image path, it dynamically takes all the images present in the directory.
The only rule to follow is to place the images in a folder named after according to the activity date (example: 2024-01-26).
You need ta adapt imgdir variable.

import os import tempfile activity = GC.activity() date = GC.activityMetrics()['date'] def list_images(dir): imglist = "" col = 0 if not os.path.exists(dir): print("Pas de photos pour cette activité.") return files = os.listdir(dir) ext = [".jpg", ".jpeg", ".png", ".gif", ".bmp"] images = [file for file in files if any( file.lower().endswith(ext) for ext in ext)] # Afficher le chemin complet de chaque fichier image for image in images: img_path = os.path.join(dir, image) col += 1 imglist += """<img src="{}" max-width: 60%>""".format(img_path) return (imglist, col) imgdir = "{}/{}/{}".format(os.getcwd(), ".goldencheetah/Jerome/media/Images", date) imglist, col = list_images(imgdir) # Count Nr of Images to dynamically adjust colums colnr = 1 if col < 3: colnr = 1 if col > 2 and col < 7: colnr = 2 if col > 6: colnr = 3 # create a temporary html file and fill it tmp = tempfile.NamedTemporaryFile(delete=False) path = tmp.name+'.html' f = open(path, 'w') txt = f""" <html> <head> <style> div {{ max-width: 1200px; margin: 0 auto; }} #photos {{ line-height: 0; -webkit-column-count: {colnr}; -webkit-column-gap: 10px; -moz-column-count: {colnr}; -moz-column-gap: 10px; column-count: {colnr}; column-gap: 10px; }} #photos img {{ width: 100%; height: auto !important; margin-bottom: 10px; }} @media (max-width: 1200px) {{ #photos {{ column-count: {colnr}; }} }} @media (max-width: 980px) {{ #photos {{ column-count: {colnr}; }} }} @media (max-width: 640px) {{ #photos {{ column-count: {colnr}; }} }} </style> </head> <body style="background-color:black;"> <div> <section id="photos"> {imglist} </section> </div> </body> </html> """ # write the html file to the GC.webpage f.write(txt) f.close() GC.webpage('file://' + path)

Jérôme launay

unread,
Jan 25, 2024, 9:07:22 PMJan 25
to golden-cheetah-users
Line 10 I have to replace return with return ("<p>No images for this activity</p>", 0)

Medien Forscher

unread,
Jan 26, 2024, 2:45:09 AMJan 26
to golden-cheetah-users
Jérôme, that's even better, thx! We are creating something here. 😊 Credit for the idea goes to Alé, I just googled a night long for code pieces 😅. 

@Manuel: thx for the hint to the platform. 

Best Olivier 

Message has been deleted

Jérôme launay

unread,
Jan 26, 2024, 7:13:26 AMJan 26
to golden-cheetah-users

I hadn’t read all the comments. Thanks Alé for the idea!!!
Here’s an improved version that displays a CSS gallery.

import os import tempfile # You need to modify the 'imgdir' variable to indicate the directory of the photos. # In this directory, you should place the photos in subdirectories named in the 'YYYY-DD-MM' format matching activity date. imgdir = os.getcwd() + "/.goldencheetah/Jerome/media/Images" m = GC.activityMetrics() date = m['date'] time = m['time'] sport = m['Sport'] title = m['Workout_Title'] def list_images(dir): imglist = "" col = 0 if not os.path.exists(dir): return ("No images for this activity", 0) files = os.listdir(dir) ext = [".jpg", ".jpeg", ".png", ".gif", ".bmp"] images = [file for file in files if any( file.lower().endswith(ext) for ext in ext)] for image in images: img_path = os.path.join(dir, image) col += 1 imglist += """ <a href="#img{}"> <img class="thumb" src="{}"> </a> <div class="lightbox" id="img{}"> <a href="#img{}" class="light-btn btn-prev">prev</a> <a href="#_" class="btn-close">X</a> <img src="{}"> <a href="#img{}" class="light-btn btn-next">next</a> </div> """.format(col,img_path,col,col-1, img_path,col+1) return (imglist, col) imgrootdir = "{}/{}".format(imgdir, date) imglist, col = list_images(imgrootdir) # create a temporary html file and fill it tmp = tempfile.NamedTemporaryFile(delete=False) path = tmp.name+'.html' f = open(path, 'w') txt = f""" <!DOCTYPE html> <html> <title>{title}</title> <head> <style> html, body {{ font-family: arial; padding: 0 2em; font-size: 18px; background: #111; color: #aaa; text-align: center; }} h1 {{ font-size: 2em; font-weight: 100; }} p {{ font-weight: 100; color: #888; margin-bottom: 45px; }} a {{ text-decoration: none; }} .thumb {{ max-height: 171px; border: solid 6px rgba(5, 5, 5, 0.8); }} .lightbox {{ position: fixed; z-index: 999; height: 0; width: 0; text-align: center; top: 0; left: 0; background: rgba(0, 0, 0, 0.8); opacity: 0; }} .lightbox img {{ max-width: 90%; max-height: 80%; margin-top: 2%; opacity: 0; }} .lightbox:target {{ outline: none; width: 100%; height: 100%; opacity: 1 !important; }} .lightbox:target img {{ border: solid 17px rgba(77, 77, 77, 0.8); opacity: 1; webkit-transition: opacity 0.6s; transition: opacity 0.6s; }} .light-btn {{ color: #fafafa; background-color: #333; border: solid 3px #777; padding: 5px 15px; border-radius: 1px; text-decoration: none; cursor: pointer; vertical-align: middle; position: absolute; top: 45%; z-index: 99; }} .light-btn:hover {{ background-color: #111; }} .btn-prev {{ left: 7%; }} .btn-next {{ right: 7%; }} .btn-close {{ position: absolute; right: 2%; top: 2%; color: #fafafa; background-color: #92001d; border: solid 5px #ef4036; padding: 10px 15px; border-radius: 1px; text-decoration: none; }} .btn-close:hover {{ background-color: #740404; }} </style> </head> <body> <h1>{title}</h1> <p>{sport} {date} {time}</p> {imglist} </body> </html> """ # write the html file to the GC.webpage f.write(txt) f.close() GC.webpage('file://' + path)

If there are no photos for the activity, it will show:

no_photos.png

If there are photos present:

photos.png

and in gallery mode:

gallery.png

Medien Forscher

unread,
Jan 26, 2024, 7:27:17 AMJan 26
to golden-cheetah-users
😁 It's getting better every day. With html/css we can create some nice things. 
Best
Olivier 

Jérôme launay

unread,
Jan 26, 2024, 2:16:01 PMJan 26
to golden-cheetah-users

Last version, no need to modify “imgdir”.
If not exist, it will create a folder “Images” in GoldenCheetach user home directory inside “Media”.
By default it will tell you how to upload your images.

screenshot-2024-01-26-200710.png

I prefer to store the images in the GoldenCheetach user home directory directory as they will be included in profile backups.

import os import tempfile # If it not exist, a folder named Images will be create # in GoldenCheetah user home inside "Media". user_home = GC.athlete()['home'] imgdir = "{}/media/Images".format(user_home) if not os.path.exists(imgdir): os.makedirs(imgdir) m = GC.activityMetrics() date = m['date'] time = m['time'] sport = m['Sport'] title = m['Workout_Title'] def list_images(dir): imglist = "" col = 0 if not os.path.exists(dir): path = imgdir + "/" + str(date) return ("""<p>No images for this activity. <br>Add them to the directory <i><a href="{}"> {}*</a></i> to link them to this activity.</p> <h5>*Copy photo folder path with "Right clic" -> "Copy link adress"</h5> """.format(path, path), 0) files = os.listdir(dir) ext = [".jpg", ".jpeg", ".png", ".gif", ".bmp"] images = [file for file in files if any( file.lower().endswith(ext) for ext in ext)] for image in images: img_path = os.path.join(dir, image) col += 1 imglist += """ <a href="#img{}"> <img class="thumb" src="{}"> </a> <div class="lightbox" id="img{}"> <a href="#img{}" class="light-btn btn-prev">prev</a> <a href="#_" class="btn-close">X</a> <img src="{}"> <a href="#img{}" class="light-btn btn-next">next</a> </div> """.format(col, img_path, col, col-1, img_path, col+1) return (imglist, col) imgrootdir = "{}/{}".format(imgdir, date) imglist, col = list_images(imgrootdir) # create a temporary html file and fill it tmp = tempfile.NamedTemporaryFile(delete=False) path = tmp.name+'.html' f = open(path, 'w') txt = f""" <!DOCTYPE html> <html> <title>{title}</title> <head> <style> html, body {{ font-family: arial; padding: 0 2em; font-size: 18px; background: #111; color: #aaa; text-align: center; }} h1 {{ font-size: 2em; font-weight: 100; }} p {{ font-weight: 100; color: #888; margin-bottom: 45px; }} a {{ color: hotpink; }} .thumb {{ max-height: 171px; border: solid 6px rgba(5, 5, 5, 0.8); }} .lightbox {{ position: fixed; z-index: 999; height: 0; width: 0; text-align: center; top: 0; left: 0; background: rgba(0, 0, 0, 0.8); opacity: 0; }} .lightbox img {{ max-width: 90%; max-height: 80%; margin-top: 2%; opacity: 0; }} .lightbox:target {{ outline: none; width: 100%; height: 100%; opacity: 1 !important; }} .lightbox:target img {{ border: solid 17px rgba(77, 77, 77, 0.8); opacity: 1; webkit-transition: opacity 0.6s; transition: opacity 0.6s; }} .light-btn {{ color: #fafafa; background-color: #333; border: solid 3px #777; padding: 5px 15px; border-radius: 1px; text-decoration: none; cursor: pointer; vertical-align: middle; position: absolute; top: 45%; z-index: 99; }} .light-btn:hover {{ background-color: #111; }} .btn-prev {{ left: 7%; }} .btn-next {{ right: 7%; }} .btn-close {{ position: absolute; right: 2%; top: 2%; color: #fafafa; background-color: #92001d; border: solid 5px #ef4036; padding: 10px 15px; border-radius: 1px; text-decoration: none; }} .btn-close:hover {{ background-color: #740404; }} </style> </head> <body> <h1>{title}</h1> <p>{sport} {date} {time}</p> {imglist} </body> </html> """ # write the html file to the GC.webpage f.write(txt) f.close() GC.webpage('file://' + path)
Message has been deleted

Jérôme launay

unread,
Jan 26, 2024, 2:21:19 PMJan 26
to golden-cheetah-users
I couldn't find how to update on CloudDB, so I uploaded the new version and deleted the first one.

Jérôme launay

unread,
Jan 26, 2024, 2:25:01 PMJan 26
to golden-cheetah-users
Medien, everything should be automatic; you just need to go to an activity, and it will display where to upload the images.
It's inside your Profile, under Media -> Images.
I think thats because I''m using Linux and folder are separated by "/" instead of "\" on Windows paths.
Let me check.

Jérôme launay

unread,
Jan 26, 2024, 2:36:16 PMJan 26
to golden-cheetah-users

Medien,

Here is an update version using Path from pathlib, it should deal with Windows path as well.

screenshot-2024-01-26-203405.png

Could you try and tell me if it's OK on Windows ?
What is the pink path displayed ?

import os import tempfile from pathlib import Path # If it not exist, a folder named Images will be create # in GoldenCheetah user home inside "Media". user_home = GC.athlete()['home'] imgdir = Path("{}/media/Images".format(user_home)) if not os.path.exists(imgdir): os.makedirs(imgdir) m = GC.activityMetrics() date = m['date'] time = m['time'] sport = m['Sport'] title = m['Workout_Title'] def list_images(dir): imglist = "" col = 0 if not os.path.exists(dir): path = str(imgdir) + "/" + str(date) return ("""<p>No images for this activity. <br>Add them to the directory <i><a href="{}"> {}*</a></i> to link them to this activity.</p> <h5>*Copy photo folder path with "Right clic" -> "Copy link adress"</h5> """.format(path, path), 0) files = os.listdir(dir) ext = [".jpg", ".jpeg", ".png", ".gif", ".bmp"] images = [file for file in files if any( file.lower().endswith(ext) for ext in ext)] for image in images: img_path = os.path.join(dir, image) col += 1 imglist += """ <a href="#img{}"> <img class="thumb" src="{}"> </a> <div class="lightbox" id="img{}"> <a href="#img{}" class="light-btn btn-prev">prev</a> <a href="#_" class="btn-close">X</a> <img src="{}"> <a href="#img{}" class="light-btn btn-next">next</a> </div> """.format(col, img_path, col, col-1, img_path, col+1) return (imglist, col) imgrootdir = "{}/{}".format(imgdir, date) imglist, col = list_images(imgrootdir) # create a temporary html file and fill it tmp = tempfile.NamedTemporaryFile(delete=False) path = tmp.name+'.html' f = open(path, 'w') txt = f""" <!DOCTYPE html> <html> <title>{title}</title> <head> <style> html, body {{ font-family: arial; padding: 0 2em; font-size: 18px; background: #111; color: #aaa; text-align: center; }} h1 {{ font-size: 2em; font-weight: 100; }} p {{ font-weight: 100; color: #888; margin-bottom: 45px; }} a {{ color: hotpink; }} .thumb {{ max-height: 171px; border: solid 6px rgba(5, 5, 5, 0.8); }} .lightbox {{ position: fixed; z-index: 999; height: 0; width: 0; text-align: center; top: 0; left: 0; background: rgba(0, 0, 0, 0.8); opacity: 0; }} .lightbox img {{ max-width: 90%; max-height: 80%; margin-top: 2%; opacity: 0; }} .lightbox:target {{ outline: none; width: 100%; height: 100%; opacity: 1 !important; }} .lightbox:target img {{ border: solid 17px rgba(77, 77, 77, 0.8); opacity: 1; webkit-transition: opacity 0.6s; transition: opacity 0.6s; }} .light-btn {{ color: #fafafa; background-color: #333; border: solid 3px #777; padding: 5px 15px; border-radius: 1px; text-decoration: none; cursor: pointer; vertical-align: middle; position: absolute; top: 45%; z-index: 99; }} .light-btn:hover {{ background-color: #111; }} .btn-prev {{ left: 7%; }} .btn-next {{ right: 7%; }} .btn-close {{ position: absolute; right: 2%; top: 2%; color: #fafafa; background-color: #92001d; border: solid 5px #ef4036; padding: 10px 15px; border-radius: 1px; text-decoration: none; }} .btn-close:hover {{ background-color: #740404; }} </style> </head> <body> <h1>{title}</h1> <p>{sport} {date} {time}</p> {imglist} </body> </html> """ # write the html file to the GC.webpage f.write(txt) f.close() GC.webpage('file://' + path)

Ale Martinez

unread,
Jan 26, 2024, 6:57:28 PMJan 26
to golden-cheetah-users
It seems you have arrived to a nice and pretty general solution, in the spirit of a collaborative FOSS, congratulations to both!

El viernes, 26 de enero de 2024 a la(s) 4:16:01 p.m. UTC-3, Jérôme launay escribió:

Last version, no need to modify “imgdir”.
If not exist, it will create a folder “Images” in GoldenCheetach user home directory inside “Media”.
By default it will tell you how to upload your images.

I think this is a good scheme, only caveat is it should use the full activity identification including start time since some users (triathletes are notorious for that) may have more than one session per day and pictures can be specific for one of them, now the folder is created automatically it does not augment the burden for the user, and a future gallery in Overview can be made backward compatible with this one.
Also beware Workout_Title is not universally available, I suggest to check it is present before using it to avoid script errors otherwise.
And, yes, there is no way to update the chart definition uploaded to ChartDB, it is ok to upload a new one and delete the older, I will mark the latest as curated once it is stable.
Thank you.

Mark Liversedge

unread,
Jan 27, 2024, 2:52:50 AMJan 27
to golden-cheetah-users
On Friday 26 January 2024 at 23:57:28 UTC Ale Martinez wrote:
 a future gallery in Overview can be made backward compatible with this one.

I think there are at least 3 bits of code needed:
1) drag and drop an image into a ride should save it into the media folder and add the filename to a metadata tag (; delimetered list of media)
2) overview item to display and scroll through images
3) think about supporting images in interval metadata too (e.g. for aero positions)

Mark

Medien Forscher

unread,
Jan 27, 2024, 12:29:15 PMJan 27
to golden-cheetah-users
Jérôme launay schrieb am Freitag, 26. Januar 2024 um 20:36:16 UTC+1:

Could you try and tell me if it's OK on Windows ?


I think it is the wrong path, the last slash should be a backslash. Also Workout_Title returns a error.

@Ale: Thx :-) 
@Mark: That would make perfect, but my coding skills are way to limited for that :-)

Best
Olivier 

Ale Martinez

unread,
Jan 27, 2024, 6:06:32 PMJan 27
to golden-cheetah-users
El sábado, 27 de enero de 2024 a la(s) 4:52:50 a.m. UTC-3, Mark Liversedge escribió:
On Friday 26 January 2024 at 23:57:28 UTC Ale Martinez wrote:
 a future gallery in Overview can be made backward compatible with this one.

I think there are at least 3 bits of code needed:
1) drag and drop an image into a ride should save it into the media folder and add the filename to a metadata tag (; delimetered list of media)

I would put them in a folder named after the activity, to be able to recover orphan images after a crash or the user choosing not to save metadata changes after image import.

Karl Billeter

unread,
Jan 27, 2024, 8:22:39 PMJan 27
to golden-che...@googlegroups.com
On Sat, Jan 27, 2024 at 09:29:14AM -0800, Medien Forscher wrote:

> Jérôme launay schrieb am Freitag, 26. Januar 2024 um 20:36:16 UTC+1:
>
> Could you try and tell me if it's OK on Windows ?
>
Thanks for your work Jérôme, I was putting off importing 12 years of Strava
photos until yesterday :-). Strava's export everything includes a media column
in `activities.csv` and a `media.csv` that also has the captions, if you gave
the images/videos captions.

Just a minor change to use `StravaID` (I'd already previously added that to all
activities) instead of `date` for the directory. It mostly worked.

> I think it is the wrong path, the last slash should be a backslash. Also
> Workout_Title returns a error.

`Workout_Title` is where I had a problem too. At first I thought it was a Python
binding issue. "Workout Title" shows in the Diary and as an Overview Tile but
is empty in the Python dictionary with an '_' and not supported with a ' '. I
also found it showed in R code, and if I added an extra TAG in the JSON activity
files (RIDE.TAGS.Workout_Title as well as RIDE.TAGS."Workout Title") then it
worked fine. However, I no longer believe this is the case anymore as on my
Linux system it works fine, so maybe it's an artifact of running through
Rosetta2 on Apple Silicon MacOS?

Anyway.. thanks again,

Karl
Message has been deleted

Fe

unread,
Jan 28, 2024, 5:45:26 AMJan 28
to golden-cheetah-users

Hi,
    alternative example with plotly  (Py chart 04)

GC-images-plotly.jpg

GC-fields-.jpg

Drag and drop or copy as path  (files ----> Data->Images)
Trial version

Regards
Fe
Py chart 04.7z

Ale Martinez

unread,
Jan 28, 2024, 7:25:28 PMJan 28
to golden-cheetah-users
El domingo, 28 de enero de 2024 a la(s) 7:45:26 a.m. UTC-3, Fe escribió:

Hi,
    alternative example with plotly  (Py chart 04)

GC-images-plotly.jpg

GC-fields-.jpg

Drag and drop or copy as path  (files ----> Data->Images)

Drag and drop the images over a TextBox field is a very clean solution to get the filenames, excellent! 
Message has been deleted

Ale Martinez

unread,
Jan 30, 2024, 3:26:16 PMJan 30
to golden-cheetah-users
In current master, Mark's added images drag and drop to GoldenCheetah, it works over any chart and works with the selected activity in Activities view, images are copied to athlete's media folder and filenames added to Images fields, so replacing the line:

    image=am['Image'].replace('"','').split()

by:
    prefix="file:///"+ath['home']+'/media/'

    image=[prefix+img for img in am['Images'].split()]

in the above chart script makes it to work with the new scheme. 

Jérôme launay

unread,
Jan 30, 2024, 7:44:56 PMJan 30
to golden-cheetah-users
Thanks a lot Ale and Mark, nice feature, I will update my chart to use these new fields.
About Workout_Title, maybe this is something I've added or I've been using from an old version.
Which field do you use for the activity title? Workout_Code ?

Jérôme launay

unread,
Jan 30, 2024, 8:44:57 PMJan 30
to golden-cheetah-users
import tempfile user_home = GC.athlete()['home'] m = GC.activityMetrics() date = m['date'] time = m['time'] sport = m['Sport'] try: title = m['Workout_Title'] except KeyError: title = m['Workout_Code'] prefix = "file:///" + user_home + '/media/' images = [prefix+img for img in m['Images'].split()] def list_images(images): imglist = "" col = 0 if not images: return ("""<p>No images for this activity. <br>drag and dropping images into a ride will store them in the media folder and add the filename to the "Images" metadata tag.</p>""", 0) for image in images: col += 1 imglist += """ <a href="#img{}"> <img class="thumb" src="{}"> </a> <div class="lightbox" id="img{}"> <a href="#img{}" class="light-btn btn-prev">prev</a> <a href="#_" class="btn-close">X</a> <img src="{}"> <a href="#img{}" class="light-btn btn-next">next</a> </div> """.format(col, image, col, col-1, image, col+1) return (imglist, col) imglist, col = list_images(images) # create a temporary html file and fill it tmp = tempfile.NamedTemporaryFile(mode="w+t", prefix="GC_", suffix=".html") path = tmp.name+'.html' f = open(path, 'w') txt = f""" <!DOCTYPE html> <html> <title>{title}</title> <head> <style> html, body {{ font-family: arial; padding: 0 2em; font-size: 18px; background: #111; color: #aaa; text-align: center; }} h1 {{ font-size: 2em; font-weight: 100; }} p {{ font-weight: 100; color: #888; margin-bottom: 45px; }} a {{ color: hotpink; }} .thumb {{ max-height: 171px; border: solid 6px rgba(5, 5, 5, 0.8); }} .lightbox {{ position: fixed; z-index: 999; height: 0; width: 0; text-align: center; top: 0; left: 0; background: rgba(0, 0, 0, 0.8); opacity: 0; }} .lightbox img {{ max-width: 90%; max-height: 80%; margin-top: 2%; opacity: 0; }} .lightbox:target {{ outline: none; width: 100%; height: 100%; opacity: 1 !important; }} .lightbox:target img {{ border: solid 17px rgba(77, 77, 77, 0.8); opacity: 1; webkit-transition: opacity 0.6s; transition: opacity 0.6s; }} .light-btn {{ color: #fafafa; background-color: #333; border: solid 3px #777; padding: 5px 15px; border-radius: 1px; text-decoration: none; cursor: pointer; vertical-align: middle; position: absolute; top: 45%; z-index: 99; }} .light-btn:hover {{ background-color: #111; }} .btn-prev {{ left: 7%; }} .btn-next {{ right: 7%; }} .btn-close {{ position: absolute; right: 2%; top: 2%; color: #fafafa; background-color: #92001d; border: solid 5px #ef4036; padding: 10px 15px; border-radius: 1px; text-decoration: none; }} .btn-close:hover {{ background-color: #740404; }} </style> </head> <body> <h1>{title}</h1> <p>{sport} {date} {time}</p> {imglist} </body> </html> """ # write the html file to the GC.webpage f.write(txt) f.close() GC.webpage('file://' + path)

Ale Martinez

unread,
Jan 30, 2024, 8:53:30 PMJan 30
to golden-cheetah-users
El martes, 30 de enero de 2024 a la(s) 9:44:56 p.m. UTC-3, Jérôme launay escribió:
Thanks a lot Ale and Mark, nice feature, I will update my chart to use these new fields.
About Workout_Title, maybe this is something I've added or I've been using from an old version.
Which field do you use for the activity title? Workout_Code ?

Currently both are included in default metadata layout, but I think Workout Code is more common in general and Workout Title for users coming from WKO/Training Peaks, IIRC.
Anyway, if you use GC.getTag instead of activityMetrics to read a metadata field it will work even if the field is not defined in metadata config, it will just return an empty string if the field doesn't have a value.
This may be handy for the Images field to work even if users don't update their metadata config:
GC.getTag("Images") instead of GC.activityMetrics()["Images"] 
in the example above.

Mark Liversedge

unread,
Jan 31, 2024, 4:01:57 AMJan 31
to golden-cheetah-users
On Wednesday 31 January 2024 at 01:53:30 UTC Ale Martinez wrote:
Currently both are included in default metadata layout, but I think Workout Code is more common in general and Workout Title for users coming from WKO/Training Peaks, IIRC.

Workout Code is definitely a generalised thing, but it is also available in WKO and mapped across.
Workout Title was introduced to map from TrainingPeaks and then Strava with a view to it being a loger version

e.g. Workout Code "2x20" to Workout Title "Threshold Intervals 2x 20 minutes"

But then everyone has their own scheme for this !

PS: I will add a gallery widget sometime and make it possible to associate an image with an interval (primarily for Aero testing where the rider position is useful when looking at the data)

Mark 

Steve Edmonds

unread,
Jan 31, 2024, 5:35:39 PMJan 31
to golden-che...@googlegroups.com


On 31/01/2024 22:01, Mark Liversedge wrote:
On Wednesday 31 January 2024 at 01:53:30 UTC Ale Martinez wrote:
Currently both are included in default metadata layout, but I think Workout Code is more common in general and Workout Title for users coming from WKO/Training Peaks, IIRC.

Workout Code is definitely a generalised thing, but it is also available in WKO and mapped across.
Workout Title was introduced to map from TrainingPeaks and then Strava with a view to it being a loger version

e.g. Workout Code "2x20" to Workout Title "Threshold Intervals 2x 20 minutes"

But then everyone has their own scheme for this !
When finishing a workout from an ERG file, DESCRIPTION in the header section maps to the Route field in the activity data tab. I use workout codes to make trending of specific workouts easier but mapping DESCRIPTION to Workout Title would seem more in line with the example above.

PS: I will add a gallery widget sometime and make it possible to associate an image with an interval (primarily for Aero testing where the rider position is useful when looking at the data)

Mark 
--
_______________________________________________
Golden-Cheetah-Users mailing list
golden-che...@googlegroups.com
http://groups.google.com/group/golden-cheetah-users?hl=en
---
You received this message because you are subscribed to the Google Groups "golden-cheetah-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golden-cheetah-u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golden-cheetah-users/6600f192-85da-4657-83e4-8fb56b3b48adn%40googlegroups.com.

Ale Martinez

unread,
Feb 3, 2024, 12:29:42 PMFeb 3
to golden-cheetah-users

    image=[prefix+img for img in GC.getTag('Images').split()]

in the above chart script makes it to work with the new scheme. 

Images drag and drop functionality, to be used with the modified charts above, is included in latest snapshot builds available for download from https://www.goldencheetah.org/#section-download, in principle they are v3.6 plus some additional functionality and bug fixes so they can be used interchangeably.

Joachim Kohlhammer

unread,
Feb 4, 2024, 2:39:06 PMFeb 4
to golden-cheetah-users
Nice to see what is happening here, I like it a lot!
To make the feature better usable for me, I have created 4 Python-Fixes:
  • cleanImages.py
    • Removes non-existant and duplicate files from the activities tag Images; before writing back, the list is sorted alphabetically
  • removeDuplicateImages.py
    • Uses opencv to find similar images added to one activity and keeps only one of them
    • The tag Images is updated automatically
  • shrinkImages.py
    • Scales down all images (using opencv) of an activity to fit into a box (currently configured as 2000x2000 pixels). This speeds up the gallery a lot
  • syncImages.py
    • Copies all images that were taken during an activity from my central photo-store to Golden Cheetahs media folder and updates the tag Images
    • All my photos are organized using the scheme <YEAR>/<MONTH>/YYYYMMDD-HHMMSS-...jpg
These scripts - especially syncImages.py - are very specific to my local setup and workflow. However I hope they can be useful for others too.
Feedback and suggestions are welcome!

Cheers
 Joachim

Ale Martinez

unread,
Feb 8, 2024, 9:25:08 AMFeb 8
to golden-cheetah-users
Nice Joachim, I think we should add an entry to FAQs for other users looking for this functionality, I added the 2 gallery charts to curated section of CloudDB with FE's one modified to accept both the original scheme (drag and drop over Image TextBox field) and the new one in latest snapshot (drag and drop over the selected activity):

Screenshot 2024-02-08 111755.png 

Fe

unread,
Feb 9, 2024, 9:19:13 AMFeb 9
to golden-cheetah-users
Hi Ale,  quick update :

## 1) Title (Sport)

## 2) Filename (mouseover image)

## 3) Margin


Py(Images-2).jpg


Problems with spaces in file names.
Thanks
Py image 07(Images-2).gchart

Ale Martinez

unread,
Feb 9, 2024, 11:54:07 AMFeb 9
to golden-cheetah-users
El viernes, 9 de febrero de 2024 a la(s) 11:19:13 a.m. UTC-3, Fe escribió:
Hi Ale,  quick update :

## 1) Title (Sport)

## 2) Filename (mouseover image)

## 3) Margin


Py(Images-2).jpg


Problems with spaces in file names.

Thank you!

I updated the CloudDB chart and added a not about not to drop the images over this chart, regrettably the chart accept drops, shows the first image but obviously this is not persistent, and it may confuse users, I did not find a way to disable this.

Richard Abbott

unread,
Apr 25, 2024, 9:57:10 AMApr 25
to golden-cheetah-users
This all looks great. and it looks from the release notes like the 3.7 DEV supports it, but  but I don't understand how to do it (the drag and drop seems to do something, but I don't see any photos). I don't find a tile to add, or whatever needs to be done.

It would be great if someone who understands it would be willing to add instructions to the FAQ.
- Rich

Ale Martinez

unread,
Apr 25, 2024, 10:14:18 AMApr 25
to golden-cheetah-users
El jueves, 25 de abril de 2024 a la(s) 10:57:10 a.m. UTC-3, Richard Abbott escribió:
This all looks great. and it looks from the release notes like the 3.7 DEV supports it, but  but I don't understand how to do it (the drag and drop seems to do something, but I don't see any photos). I don't find a tile to add, or whatever needs to be done.

It would be great if someone who understands it would be willing to add instructions to the FAQ.
- Rich

You need to add a Python chart to display photos, there are a couple available on CloudDB: View > Download Chart > Images for example.
We need to add this to FAQs and default charts. 

marcen

unread,
Apr 25, 2024, 1:16:43 PMApr 25
to golden-cheetah-users

Ale Martinez

unread,
Apr 25, 2024, 8:24:03 PMApr 25
to golden-cheetah-users
El jueves, 25 de abril de 2024 a la(s) 2:16:43 p.m. UTC-3, marcen escribió:
Thank you, Marcen.

I added Images chart to v3.7 default layout in General perspective for Analysis view, new users and user doing View > Reset Layout in Analysis View should see it by default, Images data field is convenient but not required for the chart to work.

marcen

unread,
Apr 26, 2024, 1:54:15 PMApr 26
to golden-cheetah-users
Thank you, for the hint.

I have adapted the guide. Now it is with the note that the chart in v3.7 is a standard chart.
Data field is now for maintaining the images.

If possible, I would like to add two pictures and a short video to the guide.
The data can be found in the attached zip file.

I made a small update to the image chart. Now the chart can handle filenames with spaces.
Changed the date to the local date with the weekday.
Added a description to the Path.
Also attached.
Archiv.zip
Images.gchart

Ale Martinez

unread,
Apr 26, 2024, 4:07:16 PMApr 26
to golden-cheetah-users
El viernes, 26 de abril de 2024 a la(s) 2:54:15 p.m. UTC-3, marcen escribió:
I have adapted the guide. Now it is with the note that the chart in v3.7 is a standard chart.
Data field is now for maintaining the images.

If possible, I would like to add two pictures and a short video to the guide.
The data can be found in the attached zip file.

Added to doc/wiki/Images*, thank you! 

Ale Martinez

unread,
Apr 26, 2024, 4:53:18 PMApr 26
to golden-cheetah-users
El viernes, 26 de abril de 2024 a la(s) 2:54:15 p.m. UTC-3, marcen escribió:
I made a small update to the image chart. Now the chart can handle filenames with spaces.
Changed the date to the local date with the weekday.
Added a description to the Path.

Default Images gallery chart is updated with your contribution, also fixed the problem when the drag&drop happens over a Python Web chart disabling drops.  

marcen

unread,
Apr 26, 2024, 6:17:50 PMApr 26
to golden-cheetah-users
Thank you for the upload and the fix by the Python Web chart.

Images and a move have been added to the wiki page.

Ale Martinez

unread,
Apr 26, 2024, 7:45:26 PMApr 26
to golden-cheetah-users
I pushed another fix (https://github.com/GoldenCheetah/GoldenCheetah/commit/62993743d9870fab8cca8048fc51913dec4bcf66) so the Images chart is updated after import.

The combination of the last 2 changes enables the more intuitive drag-n-drop over the Images gallery chart with immediate update, this will be available in May snapshot builds.

Ale Martinez

unread,
May 12, 2024, 7:18:02 PMMay 12
to golden-cheetah-users
El viernes, 26 de abril de 2024 a la(s) 8:45:26 p.m. UTC-3, Ale Martinez escribió:
I pushed another fix (https://github.com/GoldenCheetah/GoldenCheetah/commit/62993743d9870fab8cca8048fc51913dec4bcf66) so the Images chart is updated after import.

The combination of the last 2 changes enables the more intuitive drag-n-drop over the Images gallery chart with immediate update, this will be available in May snapshot builds.

May snapshot builds are available from https://www.goldencheetah.org/#section-download, using one of them you can drag and drop images directly over the Images chart -included in default layout and downloadable from CloudDB- and the images will be copied to the athlete's media folder and displayed in this chart. 
If you use v3.6 release build and don't want to upgrade yet, you can create an Image TextBox field and drag and drop the images there, full paths will be added and the images will not be copied, but they will be displayed on the Images chart downloaded from CloudDB.

Fe

unread,
May 13, 2024, 8:53:28 AMMay 13
to golden-cheetah-users
Hi Ale,
              drag and drop is perfect now, thanks.
 I added some options :
1) Dragmode ( mouse 'mpointer' = 'pan' or 'zoom' , now default is 'pan' )
2) Show notes (  'notes' = True or False ....  if it helps)
Write notes in the field 'Data->Images->ImgNote'.
3) Open save folder (  'opnimg' = True or False , default False)

ImagesNotes--.jpg

Thanks
Fe
Py image 09.gchart

Ale Martinez

unread,
May 14, 2024, 9:13:58 AM (14 days ago) May 14
to golden-cheetah-users
El lunes, 13 de mayo de 2024 a la(s) 9:53:28 a.m. UTC-3, Fe escribió:
Hi Ale,
              drag and drop is perfect now, thanks.
 I added some options :
1) Dragmode ( mouse 'mpointer' = 'pan' or 'zoom' , now default is 'pan' )
2) Show notes (  'notes' = True or False ....  if it helps)
Write notes in the field 'Data->Images->ImgNote'.
3) Open save folder (  'opnimg' = True or False , default False)

ImagesNotes--.jpg

Great, thank you! The chart is updated in CloudDB now. 
Reply all
Reply to author
Forward
0 new messages