Skip to content

Commit 05dfed6

Browse files
committed
initial commit
0 parents  commit 05dfed6

File tree

13 files changed

+380
-0
lines changed

13 files changed

+380
-0
lines changed

.github/workflows/update.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: Update statistics
2+
on:
3+
schedule:
4+
- cron: '50 23 * * *' # Run at 23:50 everyday
5+
workflow_dispatch:
6+
7+
jobs:
8+
update:
9+
runs-on: ubuntu-latest
10+
permissions:
11+
contents: write
12+
steps:
13+
- name: Checkout repository
14+
uses: actions/checkout@v4
15+
16+
- name: Install requirements
17+
run: pip3 install -r requirements.txt
18+
19+
- name: Extract data
20+
run: python3 extract.py
21+
22+
- name: Create plots
23+
run: |
24+
python3 plot.py
25+
python3 plot_OS.py
26+
python3 plot_file_type.py
27+
28+
- name: Create badges
29+
run: |
30+
python3 create_badges.py
31+
32+
- name: Git commit and push
33+
run: |
34+
git config --global user.email "github-actions[bot]@users.noreply.github.com"
35+
git config --global user.name "github-actions[bot]"
36+
git add data/*.json
37+
git add data/last_updated
38+
git add plots/*.svg
39+
git add badges/*.svg
40+
git commit -m "Update"
41+
git push https://x-access-token:${{ secrets.GH_TOKEN }}@github.com/${{ github.repository }}.git HEAD:${{ github.ref_name }}

LICENSE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 SINTEF and Norwegian University of Science and Technology (NTNU)
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
FAST GitHub Statistics
2+
=========================
3+
Repository for updating FAST GitHub statistics automatically using [PyGitHub](https://github.com/PyGithub/PyGithub).

badges/.gitkeep

Whitespace-only changes.

create_badges.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import json
2+
import requests
3+
import os
4+
5+
# Get data for all years
6+
data = {}
7+
for filename in os.listdir('data/'):
8+
if '-' not in filename and '.json' in filename:
9+
print(filename)
10+
with open(f'data/{filename}', 'r') as file:
11+
data.update(json.load(file))
12+
13+
sum = 0
14+
for date, value in data.items():
15+
sum += value
16+
17+
sum = round(sum / 1000, 1)
18+
url = f'https://img.shields.io/badge/GitHub_Downloads-{sum}k-green?logo=github'
19+
20+
response = requests.get(url)
21+
22+
filename = 'badges/total.svg'
23+
if response.status_code == 200:
24+
with open(filename, 'wb') as file:
25+
for chunk in response.iter_content(chunk_size=1024):
26+
file.write(chunk)
27+
print(f"Image downloaded successfully to {filename}")
28+
else:
29+
print('Failed')

data/.gitkeep

Whitespace-only changes.

extract.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import json
2+
import os
3+
from datetime import datetime
4+
from github import Github
5+
6+
year = str(datetime.now().year)
7+
date_format = '%Y-%m-%d'
8+
date_today = datetime.now().strftime(date_format)
9+
10+
# Check if today is already processed
11+
if os.path.exists('data/last_updated'):
12+
with open('data/last_updated', 'r') as file:
13+
last_updated = file.readline().strip()
14+
if last_updated == date_today:
15+
print('Today has already been processed!')
16+
exit(0)
17+
18+
# =====================> Get downloads
19+
github = Github()
20+
repo = github.get_repo('FAST-Imaging/FAST')
21+
releases = repo.get_releases()
22+
downloads_total = 0
23+
downloads_per_OS = {
24+
'Windows': 0,
25+
'Linux': 0,
26+
'macOS x86_64': 0,
27+
'macOS arm64': 0,
28+
}
29+
downloads_per_type = {
30+
'Archive': 0,
31+
'Python Wheel': 0,
32+
'Installer': 0,
33+
}
34+
for release in releases:
35+
assets = release.get_assets()
36+
for asset in assets:
37+
downloads_total += asset.download_count
38+
39+
if 'win' in asset.name:
40+
downloads_per_OS['Windows'] += asset.download_count
41+
elif 'ubuntu' in asset.name or 'linux' in asset.name:
42+
downloads_per_OS['Linux'] += asset.download_count
43+
elif 'mac' in asset.name:
44+
if 'arm64' in asset.name:
45+
downloads_per_OS['macOS arm64'] += asset.download_count
46+
else:
47+
downloads_per_OS['macOS x86_64'] += asset.download_count
48+
else:
49+
print(f'Error parsing OS for {asset.name}')
50+
continue
51+
52+
if asset.name.endswith('.tar.xz') or asset.name.endswith('.zip') or asset.name.endswith('.tar.gz'):
53+
downloads_per_type['Archive'] += asset.download_count
54+
elif asset.name.endswith('.exe') or asset.name.endswith('.deb'):
55+
downloads_per_type['Installer'] += asset.download_count
56+
elif asset.name.endswith('.whl'):
57+
downloads_per_type['Python Wheel'] += asset.download_count
58+
else:
59+
print(f'Error parsing file type for {asset.name}')
60+
continue
61+
62+
63+
# ========================> Total downloads
64+
if os.path.exists(f'data/{year}.json'):
65+
with open(f'data/{year}.json', 'r') as file:
66+
data = json.load(file)
67+
# Get sum of all
68+
sum = 0
69+
for date, value in data:
70+
sum += value
71+
data[date_today] = downloads_total - sum
72+
else:
73+
data = {date_today: downloads_total}
74+
75+
with open(f'data/{year}.json', 'w') as file:
76+
json.dump(data, file, indent=4)
77+
78+
79+
# ========================> Per OS
80+
if os.path.exists(f'data/{year}-OS.json'):
81+
with open(f'data/{year}-OS.json', 'r') as file:
82+
data = json.load(file)
83+
for OS in data.keys():
84+
# Find sum for OS
85+
sum = 0
86+
for date, value in data[OS]:
87+
sum += value
88+
data[OS][date_today] = downloads_per_OS[OS] - sum
89+
else:
90+
data = {}
91+
for OS in downloads_per_OS.keys():
92+
data[OS] = {date_today: downloads_per_OS[OS]}
93+
94+
with open(f'data/{year}-OS.json', 'w') as file:
95+
json.dump(data, file, indent=4)
96+
97+
98+
# ========================> Per file type
99+
if os.path.exists(f'data/{year}-file-type.json'):
100+
with open(f'data/{year}-file-type.json', 'r') as file:
101+
data = json.load(file)
102+
for type in data.keys():
103+
# Find sum for OS
104+
sum = 0
105+
for date, value in data[type]:
106+
sum += value
107+
data[type][date_today] = downloads_per_type[type] - sum
108+
else:
109+
data = {}
110+
for type in downloads_per_type.keys():
111+
data[type] = {date_today: downloads_per_type[type]}
112+
113+
with open(f'data/{year}-file-type.json', 'w') as file:
114+
json.dump(data, file, indent=4)
115+
116+
# =====================> Update last_updated file
117+
with open('data/last_updated', 'w') as file:
118+
file.write(date_today)

plot.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import matplotlib.pyplot as plt
2+
import json
3+
import pandas as pd
4+
import calendar
5+
from datetime import datetime
6+
from style import set_style
7+
8+
set_style(plt)
9+
10+
year = datetime.now().year
11+
12+
with open(f'data/{year}.json', 'r') as file:
13+
data = json.load(file)
14+
15+
# Skip first date
16+
if year == 2025:
17+
first_date = sorted(data.keys())[0]
18+
del data[first_date]
19+
20+
# Per month
21+
df = pd.DataFrame({'date': pd.to_datetime(list(data.keys())),
22+
'downloads': list(data.values())
23+
})
24+
# Fill missing days with 0
25+
df = df.set_index('date')
26+
full_date_range = pd.date_range(start=pd.to_datetime(f'{year}-01-01'), end=pd.Timestamp.now(), freq='D')
27+
df = df.reindex(full_date_range, fill_value=0)
28+
29+
df['month'] = df.index.to_period('M')
30+
df['month_name'] = df.index.strftime('%b')
31+
monthly_data = df.groupby('month')['downloads'].sum().reset_index()
32+
monthly_data = monthly_data.sort_values(by='month')
33+
monthly_data['month_name'] = monthly_data['month'].apply(lambda x: calendar.month_abbr[x.month])
34+
35+
fig, ax = plt.subplots(figsize=(10, 6))
36+
bars = ax.bar(monthly_data['month_name'], monthly_data['downloads'], color='skyblue')
37+
ax.bar_label(bars, label_type='edge', fmt='{:,.0f}') # Customize format as needed
38+
plt.title(f'Monthly downloads of FAST GitHub releases {year}')
39+
plt.tight_layout()
40+
fig.savefig(f'plots/{year}.svg')
41+
#plt.show()

plot_OS.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import matplotlib.pyplot as plt
2+
import json
3+
import pandas as pd
4+
import calendar
5+
from datetime import datetime
6+
from style import set_style
7+
8+
set_style(plt)
9+
10+
year = datetime.now().year
11+
12+
with open(f'data/{year}-OS.json', 'r') as file:
13+
all_data = json.load(file)
14+
15+
fig, ax = plt.subplots(figsize=(10, 6))
16+
previous = None
17+
for OS in all_data.keys():
18+
data = all_data[OS]
19+
20+
# Skip first date
21+
if year == 2025:
22+
first_date = sorted(data.keys())[0]
23+
del data[first_date]
24+
25+
if len(data) == 0:
26+
continue
27+
28+
# Per month
29+
df = pd.DataFrame({'date': pd.to_datetime(list(data.keys())),
30+
'downloads': list(data.values())
31+
})
32+
33+
# Fill missing days with 0
34+
df = df.set_index('date')
35+
full_date_range = pd.date_range(start=pd.to_datetime(f'{year}-01-01'), end=pd.Timestamp.now(), freq='D')
36+
df = df.reindex(full_date_range, fill_value=0)
37+
38+
df['month'] = df.index.to_period('M')
39+
df['month_name'] = df.index.strftime('%b')
40+
monthly_data = df.groupby('month')['downloads'].sum().reset_index()
41+
monthly_data = monthly_data.sort_values(by='month')
42+
monthly_data['month_name'] = monthly_data['month'].apply(lambda x: calendar.month_abbr[x.month])
43+
bars = ax.bar(monthly_data['month_name'], monthly_data['downloads'], bottom=previous, label=OS)
44+
if previous is None:
45+
previous = monthly_data['downloads']
46+
else:
47+
previous += monthly_data['downloads']
48+
plt.title(f'Monthly downloads of FAST GitHub releases per operating system {year}')
49+
plt.legend()
50+
plt.tight_layout()
51+
fig.savefig(f'plots/{year}-OS.svg')
52+
#plt.show()

plot_file_type.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import matplotlib.pyplot as plt
2+
import json
3+
import pandas as pd
4+
import calendar
5+
from datetime import datetime
6+
from style import set_style
7+
8+
set_style(plt)
9+
10+
year = datetime.now().year
11+
12+
with open(f'data/{year}-file-type.json', 'r') as file:
13+
all_data = json.load(file)
14+
15+
fig, ax = plt.subplots(figsize=(10, 6))
16+
previous = None
17+
for type in all_data.keys():
18+
data = all_data[type]
19+
20+
# Skip first date
21+
if year == 2025:
22+
first_date = sorted(data.keys())[0]
23+
del data[first_date]
24+
25+
if len(data) == 0:
26+
continue
27+
28+
# Per month
29+
df = pd.DataFrame({'date': pd.to_datetime(list(data.keys())),
30+
'downloads': list(data.values())
31+
})
32+
33+
# Fill missing days with 0
34+
df = df.set_index('date')
35+
full_date_range = pd.date_range(start=pd.to_datetime(f'{year}-01-01'), end=pd.Timestamp.now(), freq='D')
36+
df = df.reindex(full_date_range, fill_value=0)
37+
38+
df['month'] = df.index.to_period('M')
39+
df['month_name'] = df.index.strftime('%b')
40+
monthly_data = df.groupby('month')['downloads'].sum().reset_index()
41+
monthly_data = monthly_data.sort_values(by='month')
42+
monthly_data['month_name'] = monthly_data['month'].apply(lambda x: calendar.month_abbr[x.month])
43+
bars = ax.bar(monthly_data['month_name'], monthly_data['downloads'], bottom=previous, label=type)
44+
if previous is None:
45+
previous = monthly_data['downloads']
46+
else:
47+
previous += monthly_data['downloads']
48+
plt.title(f'Monthly downloads of FAST GitHub releases per file type {year}')
49+
plt.legend()
50+
plt.tight_layout()
51+
fig.savefig(f'plots/{year}-file-type.svg')
52+
#plt.show()

0 commit comments

Comments
 (0)