# -*- coding: utf-8 -*- # # Copyright (c) 2025 Shunta Kobayashi (Kamiya-Katase Laboratory, Science Tokyo) # # This software is released under the MIT License. # https://opensource.org/licenses/MIT """ Rigaku RAW -> Bruker(-eva) RAW converter - Reads Start/Stop/Step from Rigaku RAW at fixed offsets - Quantizes Start/Step to 2 decimals (instrument UI) - Copies Y(float32) series from Rigaku - Patches Bruker header at *all* likely places: - Force-write at fixed offsets: N (u32) @ 0x02C8 and 0x02CC (both, belt-and-suspenders) START (f64) @ 0x02D8 STEP (f64) @ 0x0378 - Additionally: replace every occurrence of the original 8-byte START/STEP sequences within the first 0x03F8 bytes (header) with the new values. - Writes: header(0x03F8) + Y(n * float32) GUI: Tkinter """ import struct, tkinter as tk from tkinter import ttk, filedialog, messagebox __version__ = "1.0.0" # ===== Rigaku (fixed) ===== RIG_OFF_START_F32 = 0x0B92 RIG_OFF_STOP_F32 = 0x0B96 RIG_OFF_STEP_F32 = 0x0B9A RIG_OFF_Y_FLOATS = 0x0C56 # ===== Bruker(-eva) (assumed common to your two templates) ===== BRK_HEADER_BYTES = 0x03F8 # 1016 BRK_OFF_N_U32_A = 0x02C8 # candidate A (seen in logs) BRK_OFF_N_U32_B = 0x02CC # candidate B (variant) BRK_OFF_START_F64 = 0x02D8 BRK_OFF_STEP_F64 = 0x0378 def read_file(p): with open(p, 'rb') as f: return f.read() def write_file(p, b): with open(p, 'wb') as f: f.write(b) def f32_at(b, off): return struct.unpack_from(' 1 else step_raw # Quantize to 2 decimals step = round(step_calc, 2) start_q = round(start, 2) stop_q = start_q + step * (n - 1) return {'start': start_q, 'stop': stop_q, 'step': step, 'n': n, 'start_raw': start, 'stop_raw': stop, 'step_raw': step_raw} def patch_header(template_head, start_deg, step_deg, n): if len(template_head) < BRK_HEADER_BYTES: raise ValueError("Template header too short") hdr = bytearray(template_head[:BRK_HEADER_BYTES]) # original sequences (for replacement sweep) orig_start_bytes = bytes(hdr[BRK_OFF_START_F64:BRK_OFF_START_F64+8]) orig_step_bytes = bytes(hdr[BRK_OFF_STEP_F64 :BRK_OFF_STEP_F64 +8]) new_start_bytes = struct.pack('= BRK_HEADER_BYTES: break h[idx:idx+8] = new8 count += 1 start = idx + 8 return count rep_start = sweep_replace(hdr, orig_start_bytes, new_start_bytes) rep_step = sweep_replace(hdr, orig_step_bytes, new_step_bytes) return bytes(hdr), rep_start, rep_step def convert(rig_path, brk_tpl_path, out_path, log): rb = read_file(rig_path) bb = read_file(brk_tpl_path) rig = parse_rigaku(rb) log("[Rigaku] Start={:.2f} Stop={:.2f} Step={:.2f} N={} (raw: s={:.6f} e={:.6f} d={:.6f})".format( rig['start'], rig['stop'], rig['step'], rig['n'], rig['start_raw'], rig['stop_raw'], rig['step_raw'])) new_hdr, rep_s, rep_d = patch_header(bb[:BRK_HEADER_BYTES], rig['start'], rig['step'], rig['n']) y = rb[RIG_OFF_Y_FLOATS : RIG_OFF_Y_FLOATS + rig['n']*4] if len(y) != rig['n']*4: raise ValueError("Rigaku Y length mismatch.") out = new_hdr + y write_file(out_path, out) # verify v_start = f64_at(out, BRK_OFF_START_F64) v_step = f64_at(out, BRK_OFF_STEP_F64) v_na = u32_at(out, BRK_OFF_N_U32_A) v_nb = u32_at(out, BRK_OFF_N_U32_B) log("[Bruker] header=0x%04X start@0x%04X=%.6f step@0x%04X=%.6f N@A=0x%04X:%d N@B=0x%04X:%d" % (BRK_HEADER_BYTES, BRK_OFF_START_F64, v_start, BRK_OFF_STEP_F64, v_step, BRK_OFF_N_U32_A, v_na, BRK_OFF_N_U32_B, v_nb)) log(" replaced copies: start=%d step=%d" % (rep_s, rep_d)) log("[OK] %s" % out_path) # ===== GUI ===== class App(tk.Tk): def __init__(self): super().__init__() self.title("Rigaku → Bruker(-eva) RAW 変換") self.geometry("900x520") pad=6 frm = ttk.Frame(self); frm.pack(fill='both', expand=True, padx=pad, pady=pad) self.var_rig = tk.StringVar() self.var_tpl = tk.StringVar() self.var_out = tk.StringVar() def add_row(r, text, var, cmd, btn="参照…"): ttk.Label(frm, text=text, width=22, anchor='e').grid(row=r, column=0, sticky='e', padx=4, pady=4) ttk.Entry(frm, textvariable=var).grid(row=r, column=1, sticky='ew', padx=4) ttk.Button(frm, text=btn, command=cmd).grid(row=r, column=2, padx=4) frm.columnconfigure(1, weight=1) add_row(0, "Rigaku RAW:", self.var_rig, self.pick_rig) add_row(1, "Bruker(-eva) テンプレ:", self.var_tpl, self.pick_tpl) add_row(2, "出力ファイル:", self.var_out, self.pick_out, "保存先…") ttk.Button(frm, text="変換する", command=self.on_go).grid(row=3, column=1, sticky='e', pady=6) self.txt = tk.Text(frm, height=18) self.txt.grid(row=4, column=0, columnspan=3, sticky='nsew') frm.rowconfigure(4, weight=1) self.log("固定スロット: Rigaku START@0x%04X STOP@0x%04X STEP@0x%04X Y@0x%04X" % (RIG_OFF_START_F32, RIG_OFF_STOP_F32, RIG_OFF_STEP_F32, RIG_OFF_Y_FLOATS)) self.log("固定スロット: Bruker START@0x%04X STEP@0x%04X N@0x%04X/0x%04X header=0x%04X" % (BRK_OFF_START_F64, BRK_OFF_STEP_F64, BRK_OFF_N_U32_A, BRK_OFF_N_U32_B, BRK_HEADER_BYTES)) def log(self, s): self.txt.insert('end', s + "\n"); self.txt.see('end'); self.update_idletasks() def pick_rig(self): p = filedialog.askopenfilename(filetypes=[("RAW files","*.raw"),("All files","*.*")]) if p: self.var_rig.set(p) def pick_tpl(self): p = filedialog.askopenfilename(filetypes=[("Bruker RAW","*.raw"),("All files","*.*")]) if p: self.var_tpl.set(p) def pick_out(self): p = filedialog.asksaveasfilename(defaultextension=".raw", filetypes=[("Bruker RAW","*.raw")]) if p: self.var_out.set(p) def on_go(self): a,b,c = self.var_rig.get().strip(), self.var_tpl.get().strip(), self.var_out.get().strip() if not (a and b and c): messagebox.showerror("不足パラメータ", "Rigaku, Brukerテンプレ, 出力ファイルを指定してください。"); return try: convert(a,b,c,self.log) except Exception as e: messagebox.showerror("変換失敗", str(e)) if __name__ == "__main__": App().mainloop()