NestDAQ User Implementation By SPADI Alliance (unofficial)
 
Loading...
Searching...
No Matches
draw_bit_field.py
1"""
2@file draw_bit_field.py
3@brief Draws a bit field diagram from a JSON schema and outputs it in various formats.
4This script reads a JSON schema file and generates a visual representation of the bit fields defined in the schema.
5It supports resolving internal and external JSON references and can handle different schema versions.
6The output includes a bit field diagram in PDF, PNG, and SVG formats, as well as a Markdown file with property details.
7@section usage Usage
8@code{.sh}
9python draw_bit_field.py <input_file> [--output_dir <output_dir>] [--version <version>]
10@endcode
11@param input_file Path to the input JSON schema file.
12@param output_dir Path to the output directory (default: 'readme-header').
13@param version Schema version to use (e.g., v0, v1) (optional).
14@section dependencies Dependencies
15- json
16- matplotlib
17- os
18- argparse
19@section author Author
20Shinsuke OTA <ota@rcnp.osaka-u.ac.jp>
21@section date Date
222023-10-06
23"""
24
25import json
26import matplotlib.pyplot as plt
27import matplotlib.patches as patches
28import os
29import argparse
30
31# 引数を解析
32parser = argparse.ArgumentParser(description='Draw bit field diagram from JSON schema.')
33parser.add_argument('input_file', type=str, help='Path to the input JSON schema file')
34parser.add_argument('--output_dir', type=str, help='Path to the output directory', default='readme-header')
35
36#parser.add_argument('output_file', type=str, help='Path to the output diagram file')
37parser.add_argument('--version', type=str, help='Schema version to use (e.g., v0, v1)', default=None)
38args = parser.parse_args()
39
40# JSONファイルを読み込む
41with open(args.input_file, 'r') as f:
42 schema = json.load(f)
43
44# バージョンが指定されている場合、そのバージョンの定義を使用
45if args.version:
46 properties = schema['definitions'][args.version]['properties']
47else:
48 properties = schema['properties']
49
50# 参照を解決する関数
51
52
53def resolve_ref(ref):
54 """
55 Resolves a JSON reference to its corresponding property.
56
57 This function handles both internal references (starting with '#') and external references
58 (containing a file path and a reference path separated by '#').
59
60 Args:
61 ref (str): The JSON reference to resolve. It can be an internal reference (e.g., '#/definitions/Property')
62 or an external reference (e.g., 'file.json#/definitions/Property').
63
64 Returns:
65 dict: The resolved property from the JSON schema.
66
67 Raises:
68 KeyError: If the reference path does not exist in the properties.
69 FileNotFoundError: If the external reference file does not exist.
70 json.JSONDecodeError: If the external reference file is not a valid JSON.
71 """
72 if ref.startswith('#'):
73 ref_path = ref.split('/')[-1]
74 return properties[ref_path]
75 else:
76 ref_file, ref_path = ref.split('#')
77 ref_file = os.path.join(os.path.dirname(args.input_file), ref_file)
78 with open(ref_file, 'r') as f:
79 ref_schema = json.load(f)
80 ref_properties = ref_schema['properties']
81 ref_path = ref_path.split('/')[-1]
82 return ref_properties[ref_path]
83
84
85def calculate_bit_length(details):
86 print(details)
87 if '$ref' in details:
88 ref_details = resolve_ref(details['$ref'])
89 return ref_details['bitLength']
90 if details['type'] == 'object':
91 return 0
92 if details['type'] == 'array':
93 items_ref = details['items'].get('$ref')
94 if items_ref:
95 ref_details = resolve_ref(items_ref)
96 return ref_details['bitLength']
97 return 0
98 return details['bitLength']
99
100
101# 画像の設定
102bit_width = 10
103bit_height = 20
104margin = 0.
105
106# ビット列の総数を計算
107total_bits = 0
108
109for prop, details in properties.items():
110 total_bits += calculate_bit_length(details)
111
112rows = (total_bits + 63) // 64 # 64ビットごとに改行
113
114# キャンバスのサイズを設定
115fig_width = bit_width * 64 + margin * 2
116fig_height = (rows + 1) * bit_height + margin * 2 # Adjust height for property names and descriptions
117
118# ビット列を描画するためのキャンバスを作成
119fig, ax = plt.subplots(figsize=(fig_width / 10, fig_height / 10))
120ax.set_xlim(0, fig_width)
121ax.set_ylim(0, fig_height)
122ax.axis('off')
123
124# ビット列を描画
125x = margin
126y = fig_height - margin - bit_height
127bit_position = 0
128
129# 一行目に64ビットを箱で表現する。箱のなかには左から順に 63 から 0 までのビット番号を描画する。
130for i in range(64):
131 rect = patches.Rectangle((x, y), bit_width, bit_height, linewidth=1, edgecolor='black', facecolor='none')
132 ax.add_patch(rect)
133 text = str(63 - i)
134 fontsize = min(bit_width, bit_height) * 3 # Adjust font size to fit within the box
135 ax.text(x + bit_width / 2, y + bit_height / 2, text, ha='center', va='center', fontsize=fontsize)
136 x += bit_width
137
138# Reset x position after drawing the first row of 64 bits
139x = margin
140y -= bit_height
141
142def draw_bit_field(x, y, bit_length, prop_name, bit_position, is_array=False):
143 remaining_bits = 64 - (bit_position % 64)
144 if bit_length > remaining_bits:
145 # Draw the first part
146 rect = patches.Rectangle((x, y), remaining_bits * bit_width, bit_height, linewidth=1, edgecolor='black', facecolor='none')
147 ax.add_patch(rect)
148 label = f"{prop_name}[]" if is_array else prop_name
149 fontsize = min(remaining_bits * bit_width, bit_height) * 3 # Adjust font size to fit within the box
150 ax.text(x + (remaining_bits * bit_width) / 2, y + bit_height / 2, label, ha='center', va='center', fontsize=fontsize)
151
152 # Move to the next line
153 x = margin
154 y -= bit_height
155 bit_length -= remaining_bits
156 bit_position += remaining_bits
157 else:
158 remaining_bits = bit_length
159
160 # Draw the remaining part
161 rect = patches.Rectangle((x, y), remaining_bits * bit_width, bit_height, linewidth=1, edgecolor='black', facecolor='none')
162 ax.add_patch(rect)
163 label = f"{prop_name}[]" if is_array else prop_name
164 fontsize = min(remaining_bits * bit_width, bit_height) * 3 # Adjust font size to fit within the box
165 ax.text(x + (remaining_bits * bit_width) / 2, y + bit_height / 2, label, ha='center', va='center', fontsize=fontsize)
166
167 return x + remaining_bits * bit_width, y, bit_position + remaining_bits
168
169for prop, details in properties.items():
170 if '$ref' in details:
171 details = resolve_ref(details['$ref'])
172 if details['type'] == 'object':
173 continue
174 if details['type'] == 'array':
175 items_ref = details['items'].get('$ref')
176 if items_ref:
177 ref_details = resolve_ref(items_ref)
178 bit_length = ref_details['bitLength']
179 x, y, bit_position = draw_bit_field(x, y, bit_length, prop, bit_position, is_array=True)
180 if bit_position % 64 == 0:
181 x = margin
182 y -= bit_height
183 continue
184 bit_length = details['bitLength']
185 x, y, bit_position = draw_bit_field(x, y, bit_length, prop, bit_position)
186 if bit_position % 64 == 0:
187 x = margin
188 y -= bit_height
189
190# 出力ファイル名とディレクトリを設定
191output_md_dir = args.output_dir
192output_pdf_dir = output_md_dir + '/pdf'
193output_png_dir = output_md_dir + '/png'
194output_svg_dir = output_md_dir + '/svg'
195os.makedirs(output_pdf_dir, exist_ok=True)
196os.makedirs(output_png_dir, exist_ok=True)
197os.makedirs(output_svg_dir, exist_ok=True)
198os.makedirs(output_md_dir, exist_ok=True)
199
200if args.version:
201 base_filename = os.path.splitext(os.path.basename(args.input_file))[0] + f'-{args.version}'
202else:
203 base_filename = os.path.splitext(os.path.basename(args.input_file))[0]
204
205output_pdf_file = os.path.join(output_pdf_dir, base_filename + '.pdf')
206output_svg_file = os.path.join(output_svg_dir, base_filename + '.svg')
207output_png_file = os.path.join(output_png_dir, base_filename + '.png')
208output_small_png_file = os.path.join(output_png_dir, base_filename + '-s.png')
209output_md_file = os.path.join(output_md_dir, base_filename + '.md')
210
211# プロパティ名と description を Markdown として出力
212with open(output_md_file, 'w') as md_file:
213 title = schema.get('title', 'No Title')
214 if (args.version):
215 title = f"{title} ({args.version})"
216 description = schema.get('description', 'No Description')
217 md_file.write(f"# {title}\n\n")
218 md_file.write(f"{description}\n\n")
219 md_file.write("| Property Name | Bit Length | Description |\n")
220 md_file.write("|---------------|------------|-------------|\n")
221 for prop, details in properties.items():
222 description = details.get('description', '')
223 bit_length = details.get('bitLength', '')
224 if '$ref' in details:
225 details = resolve_ref(details['$ref'])
226 bit_length = details.get('bitLength', bit_length)
227 if description == '':
228 description = details.get('description', '')
229 md_file.write(f"| {prop} | {bit_length} | {description} |\n")
230 # Embed the PNG image in the Markdown file
231 relative_png_path = os.path.relpath(output_png_file, start=os.path.dirname(output_md_file))
232 relative_pdf_path = os.path.relpath(output_pdf_file, start=os.path.dirname(output_md_file))
233 relative_svg_path = os.path.relpath(output_svg_file, start=os.path.dirname(output_md_file))
234 md_file.write(f"\n![Bit Field Diagram]({relative_svg_path})\n")
235 # md_file.write(f"\n[Download PDF]({relative_pdf_path})\n")
236# md_file.write(f"\n[![Bit Field Diagram]({relative_png_path})]({relative_small_png_path})\n")
237
238# 画像を保存
239plt.savefig(output_pdf_file, format='pdf')
240plt.savefig(output_png_file, format='png')
241plt.savefig(output_svg_file, format='svg')
242plt.close()
243