/** * @file visual.c * * * @brief 探索空間の可視化 (GTK/X11 を使用) * * 探索空間可視化機能は Linux でサポートされています. * "--enable-visualize" で ON にしてコンパイルできます. * なお,使用には gtk のバージョン 1.x が必要です. * 動作確認は gtk-1.2 で行いました. * * * * @brief Visualization of search space using GTK/X11. * * This visualization feature is supported on Linux. * To enable, specify "--enable-visualize" at configure. * It needs gtk version = 1.x, (tested on gtk-1.2). * * * @author Akinobu Lee * @date Mon Sep 12 01:49:44 2005 * * $Revision: 1.4 $ * */ /* * Copyright (c) 2003-2005 Shikano Lab., Nara Institute of Science and Technology * Copyright (c) 2005-2012 Julius project team, Nagoya Institute of Technology * All rights reserved */ #include #include "app.h" #ifdef VISUALIZE #include /* Window constant properties */ #define WINTITLE "Julius word trellis viewer" ///< Window title #define DEFAULT_WINDOW_WIDTH 800 ///< Default window width #define DEFAULT_WINDOW_HEIGHT 600 ///< Default window height #define CANVAS_MARGIN 16 ///< Margin of drawable #define WAVE_HEIGHT 48 ///< Height of wave drawing canvas #define WAVE_MARGIN 6 ///< Height margin of wave drawing canvas #define RESULT_HEIGHT 0 ///< Offset of text output for each node #define FONTSET "-*-fixed-medium-r-normal--10-*-*-*-*-*-*-*" ///< Fontset /* global valuables for window handling */ static GdkFont *fontset; ///< Fontset static GdkPixmap *pixmap = NULL; ///< Pixmap for the drawable static gint canvas_width; ///< Current width of the drawable static gint canvas_height; ///< Current height of the drawable /**********************************************************************/ /* view configuration switches */ /**********************************************************************/ static boolean sw_wid_axis = TRUE; ///< Y axis is wid (FALSE: score) static boolean sw_score_beam = FALSE; ///< Y axis is beam score (FALSE: normalized accumulated score static boolean sw_text = TRUE; ///< Text display on/off static boolean sw_line = TRUE; ///< Arc line display on/off static boolean sw_level_thres = FALSE; ///< Show level thres on waveform static boolean sw_hypo = FALSE; ///< Y axis is hypothesis score /**********************************************************************/ /* data to plot (1st pass / 2nd pass) */ /**********************************************************************/ static Recog *re; ///< Local pointer to the whole instance /* data to plot on 1st pass */ static BACKTRELLIS *btlocal = NULL; ///< Local pointer to the word trellis /* data to plot on 2nd pass */ static POPNODE *popped = NULL; ///< List of information for popped nodes static int pnum; ///< Total number of popped nodes static POPNODE *lastpop = NULL; ///< Pointer to the last popped node /**********************************************************************/ /* GTK color allocation */ /**********************************************************************/ #define NCOLS 15 static GdkColor cols[NCOLS] = { {0, 63000, 63000, 63000}, /* background */ {0, 0, 0, 40000}, /* waveform */ {0, 50000, 20000, 0}, /* waveform level threshold line */ {0, 0, 0, 65535}, /* begin node of word arc */ {0, 60000, 60000, 0}, /* end node of word arc */ {0, 24000, 32000, 24000}, /* line (context word / word graph) */ {0, 10000, 10000, 40000}, /* text (context word / word graph) */ {0, 50000, 54000, 50000}, /* line (word indexed trellis = all survived words) */ {0, 50000, 30000, 0}, /* line (best path) */ {0, 55535, 0, 0}, /* end node (best path) */ {0, 50000, 30000, 0}, /* text (best path) */ {0, 12000, 20000, 12000}, /* line and text (2nd pass) */ {0, 50000, 50000, 12000}, /* line and text (2nd pass popped) */ {0, 50000, 0, 0}, /* 2nd pass best */ {0, 0, 0, 0} /* shadow for text decoration */ }; typedef enum {C_BG, C_WAVEFORM, C_LEVELTHRES, C_BGN, C_END, C_LINE, C_TEXT, C_LINE_FAINT, C_LINE_BEST, C_END_BEST, C_TEXT_BEST, C_PASS2_NEXT, C_PASS2, C_PASS2_BEST, C_SHADOW} UserColors; static GdkGC *dgc = NULL; ///< gc for drawing /** * * 色の割付 * * * Assign color. * */ static void color_init() { GdkColormap *defcolmap; gboolean success[NCOLS]; defcolmap = gdk_colormap_get_system(); if (gdk_colormap_alloc_colors(defcolmap, cols, NCOLS, FALSE, TRUE, success) > 0) { fprintf(stderr, "Warning: some colors are not allocated\n"); } } /**********************************************************************/ /* graph scaling */ /**********************************************************************/ static LOGPROB *ftop = NULL; ///< Top score for each frame static LOGPROB *fbottom = NULL; ///< Bottom maximum score for each frame static LOGPROB lowest; ///< Lowest value (lower bound) static LOGPROB maxrange; ///< Maximum value of (top - bottom) static LOGPROB maxrange2; ///< Maximum distance from normalization line /** * * スケーリング用に最大スコアと最小スコアを得る. * * @param bt [in] 単語トレリス * * * Get the top and bottom scores for scaling. * * @param bt [in] word trellis * */ static void get_max_frame_score(BACKTRELLIS *bt) { int t, i; TRELLIS_ATOM *tre; LOGPROB x,y; /* allocate */ if (ftop != NULL) free(ftop); ftop = mymalloc(sizeof(LOGPROB) * bt->framelen); if (fbottom != NULL) free(fbottom); fbottom = mymalloc(sizeof(LOGPROB) * bt->framelen); /* get maxrange, ftop[], fbottom[] */ maxrange = 0.0; for (t=0;tframelen;t++) { x = LOG_ZERO; y = 0.0; for (i=0;inum[t];i++) { tre = bt->rw[t][i]; if (x < tre->backscore) x = tre->backscore; if (y > tre->backscore) y = tre->backscore; } ftop[t] = x; fbottom[t] = y; if (maxrange < x - y) maxrange = x - y; } /* get the lowest score and range around y=(lowest/framelen)x */ lowest = 0.0; for (t=0;tframelen;t++) { if (lowest > fbottom[t]) lowest = fbottom[t]; } maxrange2 = 0.0; for (t=0;tframelen;t++) { x = lowest * (float)t / (float)bt->framelen; if (ftop[t] == LOG_ZERO) continue; if (maxrange2 < abs(ftop[t] - x)) maxrange2 = abs(ftop[t] - x); if (maxrange2 < abs(fbottom[t] - x)) maxrange2 = abs(fbottom[t] - x); } } /** * * 時間フレームを X 座標値に変換する. * * @param t [in] 時間フレーム * * @return 対応する X 座標値を返す. * * * Scale X axis by time to fullfill in the canvas width. * * @param t [in] time frame * * @return the converted X position. * */ static gint scale_x(int t) { return(t * (canvas_width - CANVAS_MARGIN * 2) / btlocal->framelen + CANVAS_MARGIN); } /** * * スコアを Y 座標値に変換する. * * @param s [in] スコア * @param t [in] 対応する時間フレーム * * @return Y 座標値を返す. * * * Scale Y axis from score to fulfill in the canvas height. * * @param s [in] score to plot * @param t [in] corresponding time frame * * @return the converted Y position. * */ static gint scale_y(LOGPROB s, int t) { gint y; LOGPROB top, bottom; gint yoffset, height; if (sw_score_beam) { /* beam threshold-based: upper is the maximum score on the frame */ top = ftop[t]; if (top == LOG_ZERO) { /* no token found on the time */ bottom = top; } else { bottom = ftop[t] - maxrange; } } else { /* total score based: show around (lowest/framelen) x time */ top = lowest * (float)t / (float)btlocal->framelen + maxrange2; bottom = lowest * (float)t / (float)btlocal->framelen - maxrange2; } yoffset = CANVAS_MARGIN + RESULT_HEIGHT + (re->speechlen != 0 ? (WAVE_MARGIN + WAVE_HEIGHT): 0); height = canvas_height - yoffset - CANVAS_MARGIN; if (top <= bottom) { /* single or no token on the time */ y = yoffset; } else { y = (top - s) * height / (top - bottom) + yoffset; } return(y); } /** * * 単語IDを Y 座標値に変換する. * * @param wid [in] 単語ID * * @return Y 座標値を返す. * * * Scale Y axis from word id. * * @param wid [in] word id * * @return the converted Y position. * */ static gint scale_y_wid(WORD_ID wid) { gint y; gint yoffset, height; yoffset = CANVAS_MARGIN + RESULT_HEIGHT + (re->speechlen != 0 ? (WAVE_MARGIN + WAVE_HEIGHT) : 0); height = canvas_height - yoffset - CANVAS_MARGIN; if (wid == WORD_INVALID) { y = yoffset; } else { y = wid * height / re->model->winfo->num + yoffset; } return(y); } /**********************************************************************/ /* Draw wave data */ /**********************************************************************/ static SP16 max_level; ///< Maximum level of input waveform /** * * 波形表示用に時間をX座標に変換する. * * @param t [in] 時間フレーム * * @return 変換後の X 座標を返す. * * * Scale time to X position. * * @param t [in] time frame * * @return converted X position. * */ static gint scale_x_wave(int t) { return(t * (canvas_width - CANVAS_MARGIN * 2) / re->speechlen + CANVAS_MARGIN); } /** * * 波形表示用に振幅をY座標に変換する. * * @param x [in] 波形の振幅 * * @return 変換後の X 座標を返す. * * * Scale wave level to Y position * * @param x [in] wave level * * @return converted Y position. * */ static gint scale_y_wave(SP16 x) { return(WAVE_HEIGHT / 2 + WAVE_MARGIN - (x * WAVE_HEIGHT / (max_level * 2))); } /** * * 振幅の最大値を speech[] より求める. * * * * Get the maximum level of input waveform from speech[]. * * */ static void get_max_waveform_level() { int t; SP16 maxl; if (re->speechlen == 0) return; /* no waveform data (MFCC) */ maxl = 0; for(t=0;tspeechlen;t++) { if (maxl < abs(re->speech[t])) { maxl = abs(re->speech[t]); } } max_level = maxl; if (max_level < 3000) max_level = 3000; } /** * * 入力波形 speech[] を描画する. * * @param widget [in] 描画ウィジェット * * * Draw input waveform in speech[]. * * @param widget [in] drawing widget * */ static void draw_waveform(GtkWidget *widget) { int t; gint text_width; static char buf[20]; if (re->speechlen == 0) return; /* no waveform data (MFCC) */ /* first time, make gc for drawing */ if (dgc == NULL) { dgc = gdk_gc_new(widget->window); gdk_gc_copy(dgc, widget->style->fg_gc[GTK_STATE_NORMAL]); } /* draw frame */ gdk_gc_set_foreground(dgc, &(cols[C_WAVEFORM])); gdk_draw_rectangle(pixmap, dgc, FALSE, scale_x_wave(0), scale_y_wave(max_level), scale_x_wave(re->speechlen-1) - scale_x_wave(0), scale_y_wave(-max_level) - scale_y_wave(max_level)); if (sw_level_thres) { /* draw level threshold line */ gdk_gc_set_foreground(dgc, &(cols[C_LEVELTHRES])); gdk_draw_line(pixmap, dgc, scale_x_wave(0), scale_y_wave(re->jconf->detect.level_thres), scale_x_wave(re->speechlen-1), scale_y_wave(re->jconf->detect.level_thres)); gdk_draw_line(pixmap, dgc, scale_x_wave(0), scale_y_wave(- re->jconf->detect.level_thres), scale_x_wave(re->speechlen-1), scale_y_wave(- re->jconf->detect.level_thres)); snprintf(buf, 20, "-lv %d", re->jconf->detect.level_thres); text_width = gdk_string_width(fontset, buf) + 1; gdk_draw_string(pixmap, fontset, dgc, canvas_width - CANVAS_MARGIN - text_width - 2, scale_y_wave(-max_level) - 2, buf); } /* draw text */ snprintf(buf, 20, "max: %d", max_level); text_width = gdk_string_width(fontset, buf) + 1; gdk_gc_set_foreground(dgc, &(cols[C_WAVEFORM])); gdk_draw_string(pixmap, fontset, dgc, canvas_width - CANVAS_MARGIN - text_width - 2, scale_y_wave(max_level) + 12, buf); /* draw waveform */ for(t=1;tspeechlen;t++) { gdk_gc_set_line_attributes(dgc, 1, GDK_LINE_SOLID, GDK_CAP_BUTT, GDK_JOIN_MITER); gdk_draw_line(pixmap, dgc, scale_x_wave(t-1), scale_y_wave(re->speech[t-1]), scale_x_wave(t), scale_y_wave(re->speech[t])); } } /**********************************************************************/ /* GTK primitive functions to draw a trellis atom */ /**********************************************************************/ /** * * アークを描画する. * * @param widget [in] 描画ウィジェット * @param x1 [in] 第1点のX座標 * @param y1 [in] 第1点のY座標 * @param x2 [in] 第2点のX座標 * @param y2 [in] 第2点のY座標 * @param sw [in] 描画レベルの指定 * * * Draw an arc. * * @param widget [in] drawing widget * @param x1 [in] x of 1st point * @param y1 [in] y of 1st point * @param x2 [in] x of 2nd point * @param y2 [in] y of 2nd point * @param sw [in] draw strength level * */ static void mygdk_draw_arc(GtkWidget *widget, int x1, int y1, int x2, int y2, int sw) { int width; UserColors c; /* first time, make gc for drawing */ if (dgc == NULL) { dgc = gdk_gc_new(widget->window); gdk_gc_copy(dgc, widget->style->fg_gc[GTK_STATE_NORMAL]); } /* change arc style by sw */ switch(sw) { case 0: c = C_LINE_FAINT; width = 1; break; case 1: c = C_LINE; width = 1; break; case 2: c = C_LINE_BEST; width = 3; break; case 3: c = C_PASS2_NEXT; width = 1; break; /* next */ case 4: c = C_PASS2; width = 1; break; /* popper (realigned) */ case 5: c = C_PASS2_NEXT; width = 2; break; /* popped (original) */ case 6: c = C_PASS2_BEST; width = 3; break; default: c = C_LINE; width = 1; break; } /* draw arc line */ if (sw_line) { gdk_gc_set_line_attributes(dgc, width, GDK_LINE_SOLID, GDK_CAP_BUTT, GDK_JOIN_MITER); gdk_gc_set_foreground(dgc, &(cols[c])); gdk_draw_line(pixmap, dgc, x1, y1, x2, y2); gdk_gc_set_line_attributes(dgc, 1, GDK_LINE_SOLID, GDK_CAP_BUTT, GDK_JOIN_MITER); } /* draw begin/end point rectangle */ if (sw != 0 && sw != 5) { /* make edge */ gdk_gc_set_foreground(dgc, &(cols[C_SHADOW])); gdk_draw_rectangle(pixmap, dgc, TRUE, x1 - width/2 - 2, y1 - width/2 -2, width+4, width+4); gdk_draw_rectangle(pixmap, dgc, TRUE, x2 - width/2 - 2, y2 - width/2 -2, width+4, width+4); } gdk_gc_set_foreground(dgc, &(cols[C_BGN])); gdk_draw_rectangle(pixmap, dgc, TRUE, x1 - width/2 - 1, y1 - width/2 -1, width+2, width+2); if (c == C_LINE_BEST || c == C_PASS2_BEST) { gdk_gc_set_foreground(dgc, &(cols[C_END_BEST])); } else { gdk_gc_set_foreground(dgc, &(cols[C_END])); } gdk_draw_rectangle(pixmap, dgc, TRUE, x2 - width/2 - 1, y2 - width/2 - 1, width+2, width+2); } /** * * トレリス単語を描画するサブ関数. * * @param widget [in] 描画ウィジェット * @param tre [in] 描画するトレリス単語 * @param last_tre [in] @a tre の直前のトレリス単語 * @param sw [in] 描画の強さ * * * Sub-function to draw a trellis word. * * @param widget [in] drawing widget * @param tre [in] trellis word to be drawn * @param last_tre [in] previous word of @a tre * @param sw [in] drawing strength * */ static void draw_atom_sub(GtkWidget *widget, TRELLIS_ATOM *tre, TRELLIS_ATOM *last_tre, int sw) { int from_t; LOGPROB from_s; int from_w; /* draw word arc */ if (sw_wid_axis) { if (tre->begintime <= 0) { from_t = 0; from_w = WORD_INVALID; } else { from_t = last_tre->endtime; from_w = last_tre->wid; } mygdk_draw_arc(widget, scale_x(from_t), scale_y_wid(from_w), scale_x(tre->endtime), scale_y_wid(tre->wid), sw); } else { if (tre->begintime <= 0) { from_t = 0; from_s = 0.0; } else { from_t = last_tre->endtime; from_s = last_tre->backscore; } mygdk_draw_arc(widget, scale_x(from_t), scale_y(from_s, from_t), scale_x(tre->endtime), scale_y(tre->backscore, tre->endtime), sw); } } /** * * 第1パスのあるトレリス単語を描画する * * @param widget [in] 描画ウィジェット * @param tre [in] トレリス単語 * @param sw [in] 描画の強さ * * * Draw a trellis word at the 1st pass. * * @param widget [in] drawing widget * @param tre [in] trellis word * @param sw [in] strength of drawing * */ static void draw_atom(GtkWidget *widget, TRELLIS_ATOM *tre, int sw) { draw_atom_sub(widget, tre, tre->last_tre, sw); } /* draw word output text */ /** * * トレリス単語の単語読みを描画する. * * @param widget [in] 描画ウィジェット * @param tre [in] トレリス単語 * @param sw [in] 描画の強さ * * * Draw a word output string of a trellis word. * * @param widget [in] drawing widget * @param tre [in] trellis word * @param sw [in] strength of drawing * */ static void draw_atom_text(GtkWidget *widget, TRELLIS_ATOM *tre, int sw) { gint text_width; UserColors c; int style; int dx, dy, x, y; WORD_INFO *winfo; winfo = re->model->winfo; if (winfo->woutput[tre->wid] != NULL && strlen(winfo->woutput[tre->wid]) > 0) { switch(sw) { case 0: style = -1; break; case 1: c = C_TEXT; style = 0; break; case 2: c = C_TEXT_BEST; style = 1; break; case 3: c = C_PASS2_NEXT; style = 0; break; case 4: c = C_PASS2; style = 0; break; case 5: c = C_PASS2; style = 1; break; case 6: c = C_PASS2_BEST; style = 1; break; default: c = C_TEXT; style = 0; break; } if (style == -1) return; /* do not draw text */ text_width = gdk_string_width(fontset, winfo->woutput[tre->wid]) + 1; x = scale_x(tre->endtime) - text_width; if (sw_wid_axis) { y = scale_y_wid(tre->wid) - 4; } else { y = scale_y(tre->backscore, tre->endtime) - 4; } if (style == 1) { /* make edge */ gdk_gc_set_foreground(dgc, &cols[C_SHADOW]); for (dx = -1; dx <= 1; dx++) { for (dy = -1; dy <= 1; dy++) { if (dx == 0 && dy == 0) continue; gdk_draw_string(pixmap, fontset, dgc, x + dx, y + dy, winfo->woutput[tre->wid]); } } } gdk_gc_set_foreground(dgc, &cols[c]); gdk_draw_string(pixmap, fontset, dgc, x, y, winfo->woutput[tre->wid]); } } /**********************************************************************/ /* wrapper for narrowing atoms to be drawn */ /**********************************************************************/ static WORD_ID *wordlist = NULL; ///< List of drawn words for search static WORD_ID wordlistnum = 0; ///< Length of @a wordlist /** * * 描画単語リストの中に単語があるかどうか検索する. * * @param wid [in] 単語ID * * @return リストに見つかれば TRUE,見つからなければ FALSE を返す. * * * Check if the given word exists in the drawn word list. * * @param wid [in] word id * * @return TRUE if found in list, or FALSE if not. * */ static boolean wordlist_find(WORD_ID wid) { int left, right, mid; if (wordlistnum == 0) return FALSE; left = 0; right = wordlistnum - 1; while (left < right) { mid = (left + right) / 2; if (wordlist[mid] < wid) { left = mid + 1; } else { right = mid; } } if (wordlist[left] == wid) return TRUE; return FALSE; } /**********************************************************************/ /* Top trellis atom drawing functions including wrapper */ /* All functions below should call this */ /**********************************************************************/ /** * * 単語を描画する(描画単語リストにあるもののみ). * * @param widget [in] 描画ウィジェット * @param tre [in] 描画するトレリス単語 * @param sw [in] 描画の強さ * * * Draw a word on canvas (only words in wordlist). * * @param widget [in] drawing widget * @param tre [in] trellis word to be drawn * @param sw [in] strength of drawing * */ static void draw_atom_top(GtkWidget *widget, TRELLIS_ATOM *tre, int sw) { if (wordlistnum == 0 || wordlist_find(tre->wid)) { draw_atom(widget, tre, sw); } } /** * * 単語を描画する(描画単語リストにあるもののみ). * 単語のテキストを描画する(リストになければ描画しない). * * @param widget [in] 描画ウィジェット * @param tre [in] 描画するトレリス単語 * @param sw [in] 描画の強さ * * * 単語のテキストを描画する(描画単語リストにあるもののみ). * Draw text of a word on canvas (only words in wordlist). * * @param widget [in] drawing widget * @param tre [in] trellis word to be drawn * @param sw [in] strength of drawing * */ static void draw_atom_text_top(GtkWidget *widget, TRELLIS_ATOM *tre, int sw) { if (wordlistnum == 0 || wordlist_find(tre->wid)) { draw_atom_text(widget, tre, sw); } } /**********************************************************************/ /* Draw a set of atom according to their properties */ /**********************************************************************/ /** * * 全てのトレリス単語を描画する. * * @param widget [in] 描画ウィジェット * * * Draw all survived words in trellis. * * @param widget [in] drawing widget * */ static void draw_all_atom(GtkWidget *widget) { int t, i; TRELLIS_ATOM *tre; for (t=0;tframelen;t++) { for (i=0;inum[t];i++) { tre = btlocal->rw[t][i]; draw_atom_top(widget, tre, 0); } } if (sw_text) { for (t=0;tframelen;t++) { for (i=0;inum[t];i++) { tre = btlocal->rw[t][i]; draw_atom_text_top(widget, tre, 0); } } } } /** * * 第1パスにおいてその次単語が生き残ったトレリス単語のみ描画する. * * @param widget [in] 描画ウィジェット * * * Draw words whose next word was survived on the 1st pass. * * @param widget [in] drawing widget * */ static void draw_context_valid_atom(GtkWidget *widget) { int t, i; TRELLIS_ATOM *tre; for (t=0;tframelen;t++) { for (i=0;inum[t];i++) { tre = btlocal->rw[t][i]; if (tre->last_tre != NULL && tre->last_tre->wid != WORD_INVALID) { draw_atom_top(widget, tre->last_tre, 1); } } } if (sw_text) { for (t=0;tframelen;t++) { for (i=0;inum[t];i++) { tre = btlocal->rw[t][i]; if (tre->last_tre != NULL && tre->last_tre->wid != WORD_INVALID) { draw_atom_text_top(widget, tre->last_tre, 1); } } } } } #ifdef WORD_GRAPH /** * * 単語グラフとして描画する * * @param widget [in] 描画ウィジェット * * * Draw words as word graph. * * @param widget [in] drawing widget * */ static void draw_word_graph(GtkWidget *widget) { int t, i; TRELLIS_ATOM *tre; /* assume (1)word atoms in word graph are already marked in generate_lattice() in beam.c, and (2) backtrellis is wid-sorted */ for (t=0;tframelen;t++) { for (i=0;inum[t];i++) { tre = btlocal->rw[t][i]; if (tre->within_wordgraph) { draw_atom_top(widget, tre, 1); } } } if (sw_text) { for (t=0;tframelen;t++) { for (i=0;inum[t];i++) { tre = btlocal->rw[t][i]; if (tre->within_wordgraph) { draw_atom_text_top(widget, tre, 1); } } } } } #endif /** * * 第1パスの一位文仮説を描画する. * * @param widget [in] 描画ウィジェット * * * Draw the best path at the 1st pass. * * @param widget [in] drawing widget * */ static void draw_best_path(GtkWidget *widget) { int last_time; LOGPROB maxscore; TRELLIS_ATOM *tre, *last_tre; int i; /* look for the beginning trellis word at end of speech */ for (last_time = btlocal->framelen - 1; last_time >= 0; last_time--) { #ifdef USE_NGRAM /* it is fixed to the tail silence model (winfo->tail_silwid) */ last_tre = bt_binsearch_atom(btlocal, last_time, re->model->winfo->tail_silwid); if (last_tre != NULL) break; #else /* USE_DFA */ /* the best trellis word on the last frame (not use cp_end[]) */ maxscore = LOG_ZERO; for (i=0;inum[last_time];i++) { tre = btlocal->rw[last_time][i]; /* if (dfa->cp_end[winfo->wton[tmp->wid]] == TRUE) {*/ if (maxscore < tre->backscore) { maxscore = tre->backscore; last_tre = tre; } /* }*/ } if (maxscore != LOG_ZERO) break; #endif } if (last_time < 0) return; /* last_tre not found */ /* parse from the beginning word to find the best path */ draw_atom_top(widget, last_tre, 2); tre = last_tre; while (tre->begintime > 0) { tre = tre->last_tre; draw_atom_top(widget, tre, 2); } if (sw_text) { draw_atom_text_top(widget, last_tre, 2); tre = last_tre; while (tre->begintime > 0) { tre = tre->last_tre; draw_atom_text_top(widget, tre, 2); } } } /**********************************************************************/ /* 2nd pass drawing data collection functions */ /* will be called from search_bestfirst_main.c to gather the atoms referred to in the search process of the 2nd pass */ /**********************************************************************/ /** * * 第2パス可視化のための初期化を行う. * * @param maxhypo [in] 第2パスにおいてポップされた仮説の最大数 * * * Initialize for visualization of the 2nd pass. * * @param maxhypo [in] maximum number of popped hypothesis on the 2nd pass. * */ void visual2_init(int maxhypo) { POPNODE *p, *ptmp; int i; if (popped == NULL) { popped = (POPNODE *)mymalloc(sizeof(POPNODE) * (maxhypo + 1)); } else { for(i=0;inext; free(p); p = ptmp; } } } pnum = 1; /* for start words */ popped[0].tre = NULL; popped[0].score = LOG_ZERO; popped[0].last = NULL; popped[0].next = NULL; /* for bests */ p = lastpop; while(p) { ptmp = p->next; free(p); p = ptmp; } lastpop = NULL; } /** * * スタックから取り出した仮説をローカルに保存する. * * @param n [in] 仮説 * @param popctr [in] 現在のポップ数カウント * * * Store popped nodes to local buffer. * * @param n [in] hypothesis node * @param popctr [in] current number of popped hypo. * */ void visual2_popped(NODE *n, int popctr) { if (pnum < popctr + 1) pnum = popctr + 1; popped[popctr].tre = n->popnode->tre; popped[popctr].score = n->popnode->score; popped[popctr].last = n->popnode->last; popped[popctr].next = NULL; n->popnode = &(popped[popctr]); } /** * * 生成された仮説のノードを保存する. * * @param next [in] 生成された次単語仮説 * @param prev [in] 展開元の仮説 * @param popctr [in] 現在の生成仮説数カウンタ * * * Store generated nodes. * * @param next [in] generated next word hypothesis * @param prev [in] source hypothesis from which @a next was expanded * @param popctr [in] current popped num * */ void visual2_next_word(NODE *next, NODE *prev, int popctr) { POPNODE *new; /* allocate new popnode info */ new = (POPNODE *)mymalloc(sizeof(POPNODE)); new->tre = next->tre; new->score = next->score; /* link between previous POPNODE */ new->last = (prev) ? prev->popnode : NULL; next->popnode = new; /* store */ new->next = popped[popctr].next; popped[popctr].next = new; } /** * * ポップされた仮説候補を保存する. * * @param now [in] 文仮説 * @param winfo [in] 単語辞書 * * * Store last popped hypothesis of best hypothesis. * * @param now [in] 文仮説 * @param winfo [in] 単語辞書 * */ void visual2_best(NODE *now, WORD_INFO *winfo) { POPNODE *new; new = (POPNODE *)mymalloc(sizeof(POPNODE)); new->tre = now->popnode->tre; new->score = now->popnode->score; new->last = now->popnode->last; new->next = lastpop; lastpop = new; } /**********************************************************************/ /* Draw atoms refered at the 2nd pass */ /**********************************************************************/ /* draw 2nd pass results */ /** * * 第2パス探索中に,スタックから取り出された仮説とその次単語集合を描画する. * * @param widget 描画ウィジェット * * * Draw popped hypotheses and their next candidates appeared while search. * * @param widget 描画ウィジェット * */ static void draw_final_results(GtkWidget *widget) { POPNODE *firstp, *lastp, *p; for(firstp = lastpop; firstp; firstp = firstp->next) { if (firstp->tre != NULL) { draw_atom(widget, firstp->tre, 6); } lastp = firstp; for(p = firstp->last; p; p = p->last) { if (p->tre != NULL) { draw_atom_sub(widget, p->tre, lastp->tre, 6); draw_atom_text_top(widget, p->tre, 6); } lastp = p; } } } static LOGPROB maxscore; ///< Maximum score of popped hypotheses while search static LOGPROB minscore; ///< Minimum score of popped hypotheses while search /** * * 第2パスで出現した展開元仮説のスコアの最大値と最小値を求める. * * * * Get the maximum and minumum score of popped hypotheses appeared while search. * * */ static void get_max_hypo_score() { POPNODE *p; int i; maxscore = LOG_ZERO; minscore = 0.0; for(i=1;i popped[i].score) minscore = popped[i].score; } } /** * * 仮説表示時,仮説スコアを Y 座標値に変換する. * * @param s [in] 仮説スコア * * @return 対応するY座標を返す * * * Scale hypothesis score to Y position. * * @param s [in] hypothesis score * * @return the corresponding Y position. * */ static gint scale_hypo_y(LOGPROB s) { gint y; gint yoffset, height; yoffset = CANVAS_MARGIN + RESULT_HEIGHT + (re->speechlen != 0 ? (WAVE_MARGIN + WAVE_HEIGHT) : 0); height = canvas_height - yoffset - CANVAS_MARGIN; y = (maxscore - s) * height / (maxscore - minscore) + yoffset; return(y); } /* draw popped words */ /** * * スタックから取り出された仮説を描画する. * * @param widget [in] 描画ウィジェット * @param p [in] スタックから取り出された仮説の情報 * @param c [in] 線の色 * @param width [in] 線の幅 * @param style [in] 線のスタイル * * * Draw a popped hypothesis. * * @param widget [in] drawing widget * @param p [in] information of popped hypothesis * @param c [in] line color * @param width [in] line width * @param style [in] line style * */ static void draw_popped(GtkWidget *widget, POPNODE *p, UserColors c, int width, int style) { int text_width; gint x, y; if (p->tre == NULL) return; if (p->last != NULL && p->last->tre != NULL) { gdk_gc_set_line_attributes(dgc, width, GDK_LINE_SOLID, GDK_CAP_BUTT, GDK_JOIN_MITER); gdk_gc_set_foreground(dgc, &(cols[c])); gdk_draw_line(pixmap, dgc, scale_x(p->last->tre->endtime), scale_hypo_y(p->last->score), scale_x(p->tre->endtime), scale_hypo_y(p->score)); gdk_gc_set_line_attributes(dgc, 1, GDK_LINE_SOLID, GDK_CAP_BUTT, GDK_JOIN_MITER); } if (p->tre != NULL) { x = scale_x(p->tre->endtime); y = scale_hypo_y(p->score); if (style == 1) { gdk_gc_set_foreground(dgc, &(cols[C_SHADOW])); gdk_draw_rectangle(pixmap, dgc, TRUE, x - 3, y - 3, 7, 7); } else if (style == 2) { gdk_gc_set_foreground(dgc, &(cols[c])); gdk_draw_rectangle(pixmap, dgc, TRUE, x - 3, y - 3, 7, 7); } gdk_gc_set_foreground(dgc, &(cols[c])); gdk_draw_rectangle(pixmap, dgc, TRUE, x - 2, y - 2, 5, 5); if (p->tre->wid != WORD_INVALID) { text_width = gdk_string_width(fontset, re->model->winfo->woutput[p->tre->wid]) + 1; gdk_draw_string(pixmap, fontset, dgc, x - text_width-1, y - 5, re->model->winfo->woutput[p->tre->wid]); } } } /* draw popped words at one hypothesis expantion */ static int old_popctr; ///< popctr of previously popped hypo. /** * * 第2パスの単語展開の様子を描画する. * * @param widget [in] 描画ウィジェット * @param popctr [in] 展開仮説の番号 * * * Draw a popped word and their expanded candidates for 2nd pass replay. * * @param widget [in] drawing widget * @param popctr [in] counter of popped hypothesis to draw * */ static void draw_popnodes(GtkWidget *widget, int popctr) { POPNODE *p, *porg; if (popctr < 0 || popctr >= pnum) { fprintf(stderr, "invalid popctr (%d > %d)!\n", popctr, pnum); return; } porg = &(popped[popctr]); /* draw expanded atoms */ for(p = porg->next; p; p = p->next) { draw_popped(widget, p, C_LINE_BEST, 1, 0); } /* draw hypothesis context */ for(p = porg->last; p; p = p->last) { draw_popped(widget, p, C_PASS2_BEST, 2, 0); } draw_popped(widget, porg, C_PASS2_BEST, 3, 1); old_popctr = popctr; } /** * * 直前に draw_popnodes() で描画したのを上書きして消す. * * @param widget [in] 描画ウィジェット * * * Erase previous one drawn at draw_popnodes(), by overwriting. * * @param widget [in] drawing widget * */ static void draw_popnodes_old(GtkWidget *widget) { POPNODE *p, *porg; porg = &(popped[old_popctr]); /* draw expanded atoms */ for(p = porg->next; p; p = p->next) { draw_popped(widget, p, C_LINE_FAINT, 1, 0); } /* draw hypothesis context */ for(p = porg->last; p; p = p->last) { draw_popped(widget, p, C_PASS2, 2, 0); } draw_popped(widget, porg, C_PASS2, 3, 2); } /**********************************************************************/ /* GTK TopLevel draw/redraw functions */ /* will be called for each exposure/configure event */ /**********************************************************************/ static boolean fitscreen = TRUE; ///< フィットスクリーン指定時 TRUE /** * * キャンバスの背景を pixmap に作成する * * @param widget [in] 描画ウィジェット * * * Make the white background to the pixmap of the given canvas widget. * * @param widget [in] drawing widget * */ static void draw_background(GtkWidget *widget) { static char buf[MAX_HMMNAME_LEN]; /* first time, make gc for drawing */ if (dgc == NULL) { dgc = gdk_gc_new(widget->window); gdk_gc_copy(dgc, widget->style->fg_gc[GTK_STATE_NORMAL]); } /* clear pixmap background */ gdk_gc_set_foreground(dgc, &(cols[C_BG])); gdk_draw_rectangle(pixmap, dgc, TRUE, 0,0,canvas_width,canvas_height); /* display view mode and zoom status */ gdk_gc_set_foreground(dgc, &(cols[C_TEXT])); if (sw_hypo) { gdk_draw_string(pixmap, fontset, dgc, 0, canvas_height - 16, "Hypothesis score (2nd pass)"); } else { gdk_draw_string(pixmap, fontset, dgc, 0, canvas_height - 16, sw_wid_axis ? "Word ID" : (sw_score_beam ? "Beam score" : "Accumulated score (normalized by time)")); } snprintf(buf, 50, "x%3.1f", (float)canvas_width / (float)btlocal->framelen); gdk_draw_string(pixmap, fontset, dgc, 0, canvas_height - 3, buf); } /** * * 描画キャンパス内に表示すべき全ての単語情報を pixmap に描画する. * * @param widget * * * Draw all the contents to the pixmap. * * @param widget * */ static void drawarea_draw(GtkWidget *widget) { /* allocate (new) pixmap */ if (pixmap) { gdk_pixmap_unref(pixmap); /* destroy old one */ } pixmap = gdk_pixmap_new(widget->window, canvas_width, canvas_height, -1); /* make background */ draw_background(widget); if (re->speechlen != 0) { draw_waveform(widget); } if (!sw_hypo) { if (btlocal != NULL) { /* draw objects */ draw_all_atom(widget); #ifdef WORD_GRAPH draw_word_graph(widget); #else draw_context_valid_atom(widget); #endif draw_best_path(widget); } if (popped != NULL) { /* draw 2nd pass objects */ draw_final_results(widget); } } } /** * * Expose イベントを発行する. * * @param widget [in] 描画ウィジェット * * * Tell X to issue expose event on this window. * * @param widget [in] drawing widget * */ static void drawarea_expose(GtkWidget *widget) { GdkRectangle r; r.x = 0; r.y = 0; r.width = canvas_width; r.height = canvas_height; gtk_widget_draw(widget, &r); } /** * * Expose イベント処理:あらかじめ描画された pixmap を画面に表示する. * * @param widget [in] 描画ウィジェット * @param event [in] イベント情報 * @param user_data [in] ユーザデータ(未使用) * * @return gboolean を返す. * * * Expose event handler: show the already drawn pixmap to the screen. * * @param widget [in] drawing widget * @param event [in] event information * @param user_data [in] user data (unused) * * @return gboolean value. * */ static gboolean event_drawarea_expose(GtkWidget *widget, GdkEventExpose *event, gpointer user_data) { if (pixmap != NULL) { gdk_draw_pixmap(widget->window, widget->style->fg_gc[GTK_STATE_NORMAL], pixmap, event->area.x, event->area.y, event->area.x, event->area.y, event->area.width, event->area.height); } } /** * * Configure イベント処理:resize または map 時に再描画する. * * @param widget [in] 描画ウィジェット * @param event [in] イベント情報 * @param user_data [in] ユーザデータ(未使用) * * @return gboolean を返す. * * * Configure event handler: redraw objects when resized or mapped. * * @param widget [in] drawing widget * @param event [in] event information * @param user_data [in] user data (unused) * * @return gboolean value. * */ static gboolean event_drawarea_configure(GtkWidget *widget, GdkEventExpose *event, gpointer user_data) { if (fitscreen) { /* if in zoom mode, resizing window does not cause resize of the canvas */ canvas_width = widget->allocation.width; /* get canvas size */ } /* canvas height will be always automatically changed by resizing */ canvas_height = widget->allocation.height; /* redraw objects to pixmap */ drawarea_draw(widget); } /**********************************************************************/ /* GTK callbacks for buttons */ /**********************************************************************/ /** * * [show threshold] ボタンクリック時の callback: トリガしきい値線の描画 * の ON/ OFF. * * @param widget [in] 描画ウィジェット * * * Callback when [show threshold] button is clicked: toggle trigger * threshold line. * * @param widget [in] drawing widget * */ static void action_toggle_thres(GtkWidget *widget) { if (re->speechlen == 0) return; /* toggle switch */ if (sw_level_thres) sw_level_thres = FALSE; else sw_level_thres = TRUE; /* redraw objects to pixmap */ drawarea_draw(widget); /* tell X to issue expose event on this window */ drawarea_expose(widget); } #ifdef PLAYCOMMAND /** * * [play] ボタンクリック時の callback: 音声を再生する. * * @param widget [in] 描画ウィジェット * * * Callback when [play] button is clicked: play waveform. * * @param widget [in] drawing widget * */ static void action_play_waveform(GtkWidget *widget) { char buf[80]; static char command[250]; int fd; if (re->speechlen == 0) return; /* play waveform */ snprintf(buf, 250, "/var/tmp/julius_visual_play.%d", getpid()); if ((fd = open(buf, O_CREAT | O_TRUNC | O_WRONLY, 0644)) < 0) { fprintf(stderr, "cannot open %s for writing\n", buf); return; } if (wrsamp(fd, re->speech, re->speechlen) < 0) { fprintf(stderr, "failed to write to %s for playing\n", buf); return; } close(fd); snprintf(command, 250, PLAYCOMMAND, re->jconf->analysis.para.smp_freq, buf); printf("play: [%s]\n", command); system(command); unlink(buf); } #endif /** * * [Word view] ボタンクリック時の callback: Y軸を単語IDにする. * * @param button [in] ボタンウィジェット * @param widget [in] 描画ウィジェット * * * Callback when [Word view] button is clicked: set Y axis to word ID. * * @param button [in] button widget * @param widget [in] drawing widget * */ static void action_view_wid(GtkWidget *button, GtkWidget *widget) { if (GTK_TOGGLE_BUTTON(button)->active) { /* set switch */ sw_wid_axis = TRUE; sw_hypo = FALSE; /* redraw objects to pixmap */ drawarea_draw(widget); /* tell X to issue expose event on this window */ drawarea_expose(widget); } else { sw_wid_axis = FALSE; } } /** * * [Score view] ボタンクリック時の callback: Y軸をスコアにする. * * @param button [in] ボタンウィジェット * @param widget [in] 描画ウィジェット * * * Callback when [Score view] button is clicked: set Y axis to score. * * @param button [in] button widget * @param widget [in] drawing widget * */ static void action_view_score(GtkWidget *button, GtkWidget *widget) { if (GTK_TOGGLE_BUTTON(button)->active) { /* set switch */ sw_score_beam = FALSE; sw_hypo = FALSE; /* redraw objects to pixmap */ drawarea_draw(widget); /* tell X to issue expose event on this window */ drawarea_expose(widget); } } /** * * [Beam view] ボタンクリック時の callback: Y軸をフレームごとの最大スコア * からの差分にする. * * @param button [in] ボタンウィジェット * @param widget [in] 描画ウィジェット * * * Callback when [Beam view] button is clicked: set Y axis to offsets * from the maximum score on each frame. * * @param button [in] button widget * @param widget [in] drawing widget * */ static void action_view_beam(GtkWidget *button, GtkWidget *widget) { if (GTK_TOGGLE_BUTTON(button)->active) { /* set switch */ sw_score_beam = TRUE; sw_hypo = FALSE; /* redraw objects to pixmap */ drawarea_draw(widget); /* tell X to issue expose event on this window */ drawarea_expose(widget); } } /** * * [Arc on/off] ボタンクリック時の callback: 単語先頭と単語終端の間の線の * 描画を on/off する. * * @param button [in] ボタンウィジェット * @param widget [in] 描画ウィジェット * * * Callback when [Arc on/off] button is clicked: toggle lines between * word head node and word tail node. * * @param button [in] button widget * @param widget [in: drawing widget * */ static void action_toggle_arc(GtkWidget *button, GtkWidget *widget) { if (GTK_TOGGLE_BUTTON(button)->active) { sw_text = TRUE; sw_line = TRUE; } else { sw_text = FALSE; sw_line = FALSE; } /* redraw objects to pixmap */ drawarea_draw(widget); /* tell X to issue expose event on this window */ drawarea_expose(widget); } /** * * テキストウィジェットに単語名が入力されたときのcallback: 描画単語リストを * 更新する. * * @param widget [in] テキストウィジェット * @param draw [in] 描画ウィジェット * * * Callback when a word string is entered on the text widget: update the * drawing word list to show only the word. * * @param widget [in] text widget * @param draw [in] drawing widget * */ static void action_set_wid(GtkWidget *widget, GtkWidget *draw) { gchar *entry_text; WORD_ID i; entry_text = gtk_entry_get_text(GTK_ENTRY(widget)); /* allocate */ if (wordlist == NULL) { wordlist = mymalloc(sizeof(WORD_ID) * re->model->winfo->num); } wordlistnum = 0; /* pickup words with the specified output text and regiter them to the lsit */ if (strlen(entry_text) == 0) { wordlistnum = 0; } else { for (i=0;imodel->winfo->num;i++) { if (strmatch(entry_text, re->model->winfo->woutput[i])) { wordlist[wordlistnum] = i; wordlistnum++; } } if (wordlistnum == 0) { fprintf(stderr, "word \"%s\" not found, show all\n", entry_text); } else { fprintf(stderr, "%d words found for \"%s\"\n", wordlistnum, entry_text); } } /* redraw objects to pixmap */ drawarea_draw(draw); /* tell X to issue expose event on this window */ drawarea_expose(draw); } /** * * [x2] ズームボタンクリック時のcallback: X軸を2倍に伸張する. なおFITSCREENは * OFFになる. * * @param widget [in] 描画ウィジェット * * * Callback when [x2] zoom button is clicked: expand X axis to "x2". * If FITSCREEN is enabled, it will be disabled. * * @param widget [in] drawing widget * */ static void action_zoom(GtkWidget *widget) { fitscreen = FALSE; if (btlocal != NULL) { canvas_width = btlocal->framelen * 2; gtk_drawing_area_size(GTK_DRAWING_AREA(widget), canvas_width, canvas_height); } drawarea_draw(widget); drawarea_expose(widget); } /** * * [x4] ズームボタンクリック時のcallback: X軸を4倍に伸張する. なおFITSCREENは * OFFになる. * * @param widget [in] 描画ウィジェット * * * Callback when [x4] zoom button is clicked: expand X axis to "x4". * If FITSCREEN is enabled, it will be disabled. * * @param widget [in] drawing widget * */ static void action_zoom_4(GtkWidget *widget) { fitscreen = FALSE; if (btlocal != NULL) { canvas_width = btlocal->framelen * 4; gtk_drawing_area_size(GTK_DRAWING_AREA(widget), canvas_width, canvas_height); } drawarea_draw(widget); drawarea_expose(widget); } /** * * [x8] ズームボタンクリック時のcallback: X軸を8倍に伸張する. なおFITSCREENは * OFFになる. * * @param widget [in] 描画ウィジェット * * * Callback when [x8] zoom button is clicked: expand X axis to "x8". * If FITSCREEN is enabled, it will be disabled. * * @param widget [in] drawing widget * */ static void action_zoom_8(GtkWidget *widget) { fitscreen = FALSE; if (btlocal != NULL) { canvas_width = btlocal->framelen * 8; gtk_drawing_area_size(GTK_DRAWING_AREA(widget), canvas_width, canvas_height); } drawarea_draw(widget); drawarea_expose(widget); } /** * * [Fit] ボタンクリック時のコールバック:キャンパスサイズをウィンドウサイズに * 自動的に合わせるようにする. 他の zoom 指定時,それらを off にする. * * @param widget * * * Callback for [Fit] button: make canvas automatically fit to the window size. * If other zoom mode is enabled, they will be disabled. * * @param widget * */ static void action_fit_screen(GtkWidget *widget) { fitscreen = TRUE; canvas_width = widget->parent->allocation.width; gtk_drawing_area_size(GTK_DRAWING_AREA(widget), canvas_width, canvas_height); drawarea_draw(widget); drawarea_expose(widget); } /** * * 第2パス再現用の [start] ボタンのcallback: 第2パス再現用に準備する. * * @param button [in] ボタンウィジェット * @param widget [in] 描画ウィジェット * * * Callback for [start] button for pass2 replay: prepare flag and canvas * for the replaying of the 2nd pass. * * @param button [in] button widget * @param widget [in] drawing widget * */ static void action_toggle_popctr(GtkWidget *button, GtkWidget *widget) { if (GTK_TOGGLE_BUTTON(button)->active) { sw_hypo = TRUE; } else { sw_hypo = FALSE; } drawarea_draw(widget); drawarea_expose(widget); } /** * * 第2パス再現用のスケールのcallback: 値が N に変更されたときに, * N 番目の単語展開の様子を描画する. * * @param adj [in] アジャスタ * @param widget [in] 描画ウィジェット * * * Callback for pop counter scale for pass2 replay: when the scale value * was changed to N, draw the details of the Nth word expansion on the * 2nd pass. * * @param adj [in] adjuster of the scale * @param widget [in] drawing widget * */ static void action_change_popctr(GtkAdjustment *adj, GtkWidget *widget) { int popctr; if (sw_hypo) { popctr = adj->value; draw_popnodes_old(widget); draw_popnodes(widget, popctr); drawarea_expose(widget); } } /**********************************************************************/ /* GTK delete/destroy event handler */ /**********************************************************************/ /** * * GTKの削除イベントハンドラ * * @param widget [in] ウィジェット * @param event [in] イベント * @param data [in] ユーザデータ * * @return FALSEを常に返す(destroy signalを発行するため). * * * GTK delete event handler. * * @param widget [in] widget * @param event [in] event * @param data [in] user data * * @return always FALSE, to emit destroy signal. * */ static gint delete_event(GtkWidget *widget, GdkEvent *event, gpointer data) { return (FALSE); /* emit destroy signal */ } /** * * GTKの終了イベントハンドラ. アプリケーションを終了する. * * @param widget [in] ウィジェット * @param data [in] ユーザデータ * * * GTK destroy event handler. Quit application here. * * @param widget [in] widget * @param data [in] user data * */ static void destroy(GtkWidget *widget, gpointer data) { gtk_main_quit(); } /**********************************************************************/ /* Main public functions for visualization */ /**********************************************************************/ /** * * 起動時,可視化機能を初期化する. * * * Initialize visualization functions at startup. * */ void visual_init(Recog *recog) { POPNODE *p; /* hold recognition instance to local */ re = recog; /* reset values */ btlocal = NULL; /* initialize Gtk/Gdk libraries */ /* no argument passed as gtk options */ /*gtk_init (&argc, &argv);*/ gtk_init(NULL, NULL); /* set locale */ gtk_set_locale(); /* load fontset */ fontset = gdk_fontset_load(FONTSET); if (fontset == NULL) { fprintf(stderr, "cannot load X font \"%s\" for visualize\n", FONTSET); exit(-1); } /* initialize color */ color_init(); fprintf(stderr, "GTK initialized\n"); } /** * * 認識結果を元に,探索空間の可視化を実行する. * * @param bt [in] 単語トレリス * * * Start visualization of recognition result. * * @param bt [in] word trellis * */ void visual_show(BACKTRELLIS *bt) { GtkWidget *window, *button, *draw, *entry, *scrolled_window, *scale; GtkWidget *box1, *box2, *label, *frame, *box3; GtkObject *adj; GSList *group; GList *glist; fprintf(stderr, "*** Showing word trellis view (close window to proceed)\n"); /* store pointer to backtrellis data */ btlocal = bt; /* prepare for Y axis score normalization */ get_max_frame_score(bt); /* prepare for Y axis hypo score normalization */ get_max_hypo_score(); /* prepare for waveform */ if (re->speechlen != 0) get_max_waveform_level(); /* start with trellis view */ sw_hypo = FALSE; /* reset value */ fitscreen = TRUE; if (dgc != NULL) { gdk_gc_unref(dgc); dgc = NULL; } /* create main window */ window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_widget_set_usize(GTK_WIDGET(window), DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT); gtk_window_set_title(GTK_WINDOW(window), WINTITLE); gtk_signal_connect(GTK_OBJECT(window), "delete_event", GTK_SIGNAL_FUNC(delete_event), NULL); gtk_signal_connect(GTK_OBJECT(window), "destroy", GTK_SIGNAL_FUNC(destroy), NULL); gtk_container_border_width(GTK_CONTAINER(window), 10); /* create horizontal packing box */ box1 = gtk_hbox_new(FALSE, 5); gtk_container_add(GTK_CONTAINER(window), box1); /* create scrolled window */ scrolled_window = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), GTK_POLICY_ALWAYS,GTK_POLICY_AUTOMATIC); /* create drawing area */ draw = gtk_drawing_area_new(); /* gtk_drawing_area_size(GTK_DRAWING_AREA(draw), DEFAULT_CANVAS_WIDTH, DEFAULT_CANVAS_HEIGHT);*/ gtk_signal_connect(GTK_OBJECT(draw), "expose-event", GTK_SIGNAL_FUNC(event_drawarea_expose), NULL); gtk_signal_connect(GTK_OBJECT(draw), "configure-event", GTK_SIGNAL_FUNC(event_drawarea_configure), NULL); gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window), draw); gtk_box_pack_start(GTK_BOX(box1), scrolled_window, TRUE, TRUE, 0); /* create packing box for buttons */ box2 = gtk_vbox_new(FALSE, 5); gtk_box_pack_start(GTK_BOX(box1), box2, FALSE, TRUE, 0); if (re->speechlen != 0) { /* create waveform related frame */ frame = gtk_frame_new("waveform"); gtk_box_pack_start(GTK_BOX(box2), frame, FALSE, FALSE, 0); box3 = gtk_hbox_new(FALSE, 5); gtk_container_set_border_width(GTK_CONTAINER(box3), 5); gtk_container_add(GTK_CONTAINER(frame), box3); /* create play button if supported */ #ifdef PLAYCOMMAND button = gtk_button_new_with_label("play"); gtk_signal_connect_object(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(action_play_waveform), GTK_OBJECT(draw)); gtk_box_pack_start(GTK_BOX(box3), button, FALSE, FALSE, 0); #endif /* create level thres toggle button */ button = gtk_button_new_with_label("thres"); gtk_signal_connect_object(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(action_toggle_thres), GTK_OBJECT(draw)); gtk_box_pack_start(GTK_BOX(box3), button, FALSE, FALSE, 0); } /* create scaling frame */ frame = gtk_frame_new("change view"); gtk_box_pack_start(GTK_BOX(box2), frame, FALSE, FALSE, 0); box3 = gtk_hbox_new(FALSE, 5); gtk_container_set_border_width(GTK_CONTAINER(box3), 5); gtk_container_add(GTK_CONTAINER(frame), box3); /* create word view button */ button = gtk_radio_button_new_with_label(NULL, "word"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE); gtk_signal_connect(GTK_OBJECT(button), "toggled", GTK_SIGNAL_FUNC(action_view_wid), GTK_OBJECT(draw)); gtk_box_pack_start(GTK_BOX(box3), button, FALSE, FALSE, 0); /* create score view button */ group = gtk_radio_button_group(GTK_RADIO_BUTTON(button)); button = gtk_radio_button_new_with_label(group, "score"); gtk_signal_connect(GTK_OBJECT(button), "toggled", GTK_SIGNAL_FUNC(action_view_score), GTK_OBJECT(draw)); gtk_box_pack_start(GTK_BOX(box3), button, FALSE, FALSE, 0); /* create beam view button */ group = gtk_radio_button_group(GTK_RADIO_BUTTON(button)); button = gtk_radio_button_new_with_label(group, "beam"); gtk_signal_connect(GTK_OBJECT(button), "toggled", GTK_SIGNAL_FUNC(action_view_beam), GTK_OBJECT(draw)); gtk_box_pack_start(GTK_BOX(box3), button, FALSE, FALSE, 0); /* create show/hide frame */ frame = gtk_frame_new("show/hide"); gtk_box_pack_start(GTK_BOX(box2), frame, FALSE, FALSE, 0); box3 = gtk_vbox_new(FALSE, 5); gtk_container_set_border_width(GTK_CONTAINER(box3), 5); gtk_container_add(GTK_CONTAINER(frame), box3); /* create text toggle button */ button = gtk_toggle_button_new_with_label("arcs"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE); gtk_signal_connect(GTK_OBJECT(button), "toggled", GTK_SIGNAL_FUNC(action_toggle_arc), GTK_OBJECT(draw)); gtk_box_pack_start(GTK_BOX(box3), button, FALSE, FALSE, 0); /* create word entry frame */ frame = gtk_frame_new("view words"); gtk_box_pack_start(GTK_BOX(box2), frame, FALSE, FALSE, 0); box3 = gtk_vbox_new(FALSE, 5); gtk_container_set_border_width(GTK_CONTAINER(box3), 5); gtk_container_add(GTK_CONTAINER(frame), box3); /* create word ID entry */ entry = gtk_entry_new_with_max_length(16); gtk_signal_connect(GTK_OBJECT(entry), "activate", GTK_SIGNAL_FUNC(action_set_wid), GTK_OBJECT(draw)); gtk_box_pack_start(GTK_BOX(box3), entry, FALSE, FALSE, 0); /* create zoom frame */ frame = gtk_frame_new("zoom"); gtk_box_pack_start(GTK_BOX(box2), frame, FALSE, FALSE, 0); box3 = gtk_hbox_new(FALSE, 5); gtk_container_set_border_width(GTK_CONTAINER(box3), 5); gtk_container_add(GTK_CONTAINER(frame), box3); /* create x zoom button */ button = gtk_button_new_with_label("x2"); gtk_signal_connect_object(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(action_zoom), GTK_OBJECT(draw)); gtk_box_pack_start(GTK_BOX(box3), button, FALSE, FALSE, 0); /* create x zoom button */ button = gtk_button_new_with_label("x4"); gtk_signal_connect_object(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(action_zoom_4), GTK_OBJECT(draw)); gtk_box_pack_start(GTK_BOX(box3), button, FALSE, FALSE, 0); /* create x more zoom button */ button = gtk_button_new_with_label("x8"); gtk_signal_connect_object(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(action_zoom_8), GTK_OBJECT(draw)); gtk_box_pack_start(GTK_BOX(box3), button, FALSE, FALSE, 0); /* create fit screen button */ button = gtk_button_new_with_label("fit"); gtk_signal_connect_object(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(action_fit_screen), GTK_OBJECT(draw)); gtk_box_pack_start(GTK_BOX(box3), button, FALSE, FALSE, 0); /* create replay frame */ frame = gtk_frame_new("pass2 replay"); gtk_box_pack_start(GTK_BOX(box2), frame, FALSE, FALSE, 0); box3 = gtk_vbox_new(FALSE, 5); gtk_container_set_border_width(GTK_CONTAINER(box3), 5); gtk_container_add(GTK_CONTAINER(frame), box3); adj = gtk_adjustment_new(0.0, 0.0, (pnum-1) + 5, 1.0, 1.0, 5.0); gtk_signal_connect(GTK_OBJECT(adj), "value_changed", GTK_SIGNAL_FUNC(action_change_popctr), GTK_OBJECT(draw)); /* create replay start button */ button = gtk_toggle_button_new_with_label("start"); gtk_signal_connect(GTK_OBJECT(button), "toggled", GTK_SIGNAL_FUNC(action_toggle_popctr), GTK_OBJECT(draw)); gtk_box_pack_start(GTK_BOX(box3), button, FALSE, FALSE, 0); /* create replay scale widget */ scale = gtk_hscale_new(GTK_ADJUSTMENT(adj)); gtk_scale_set_digits(GTK_SCALE(scale), 0); gtk_box_pack_start(GTK_BOX(box3), scale, FALSE, FALSE, 0); /* create close button */ button = gtk_button_new_with_label("close"); /* connect click event to close the window */ gtk_signal_connect_object(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy), GTK_OBJECT(window)); gtk_box_pack_start(GTK_BOX(box2), button, FALSE, FALSE, 0); /* show all the widget */ gtk_widget_show_all(window); /* enter the gtk event routine */ gtk_main(); } #endif /* VISUALIZE */