#include <bits/stdc++.h>
using namespace std;
template <int mod>
struct Hash {
  int h;
  inline void init(const char s[], int len) {
    h = 0;
    for (int i = 1; i <= len; ++i) h = ((long long)h * 2 + (s[i] == '#')) % mod;
  }
  inline bool operator==(const Hash &b) const { return h == b.h; }
};
struct THash {
  Hash<1000000007> h1;
  Hash<1130000317> h2;
  Hash<1000000011> h3;
  inline void init(const char s[], int len) {
    h1.init(s, len);
    h2.init(s, len);
    h3.init(s, len);
  }
  inline bool operator==(const THash &b) const {
    return h1 == b.h1 && h2 == b.h2 && h3 == b.h3;
  }
};
struct Point {
  int x, y;
  inline Point(int _x = 0, int _y = 0) : x(_x), y(_y) {}
};
struct Rect {
  Point LT, RB;
  inline Rect(const Point &_LT, const Point &_RB) : LT(_LT), RB(_RB) {}
};
struct no_solution {
  const char *const info;
  inline no_solution(const char *const _info) : info(_info) {}
};
struct solution {
  Rect a, b;
  inline solution(const Rect &_a, const Rect &_b) : a(_a), b(_b) {}
};
const int N = 1000 + 5, M = 1000 + 5;
int n, m;
char rd_map[N][M];
inline void read_data() {
  scanf("%d%d", &n, &m);
  for (int i = 1; i <= n; ++i) scanf("%s", rd_map[i] + 1);
}
const int DISTING = 30 + 5;
int dx[DISTING];
int sz_dx;
THash t_hash_dx[N];
int t_dx_cnt[N];
inline void disting_X() {
  for (int i = 1; i <= n; ++i) t_hash_dx[i].init(rd_map[i], m);
  for (int i = 1; i <= n; ++i) {
    if (i > 1 && t_hash_dx[dx[sz_dx]] == t_hash_dx[i]) {
      t_dx_cnt[i] = t_dx_cnt[dx[sz_dx]] + 1;
    } else
      t_dx_cnt[i] = 1;
    if (t_dx_cnt[i] > 5)
      dx[sz_dx] = i;
    else {
      dx[++sz_dx] = i;
      if (sz_dx > 30) throw no_solution("��ϣx����");
    }
  }
}
int dy[DISTING];
int sz_dy;
THash t_hash_dy[M];
char t_s_dy[DISTING];
int t_dy_cnt[M];
inline void disting_Y() {
  for (int i = 1; i <= m; ++i) {
    for (int j = 1; j <= sz_dx; ++j) t_s_dy[j] = rd_map[dx[j]][i];
    t_hash_dy[i].init(t_s_dy, sz_dx);
  }
  for (int i = 1; i <= m; ++i) {
    if (i > 1 && t_hash_dy[dy[sz_dy]] == t_hash_dy[i]) {
      t_dy_cnt[i] = t_dy_cnt[dy[sz_dy]] + 1;
    } else
      t_dy_cnt[i] = 1;
    if (t_dy_cnt[i] > 5)
      dy[sz_dy] = i;
    else {
      dy[++sz_dy] = i;
      if (sz_dy > 30) throw no_solution("��ϣy����");
    }
  }
}
char d_map[DISTING][DISTING];
inline void make_d_map() {
  for (int i = 1; i <= sz_dx; ++i)
    for (int j = 1; j <= sz_dy; ++j) {
      d_map[i][j] = rd_map[dx[i]][dy[j]];
    }
}
inline void distinguish() {
  disting_X();
  disting_Y();
  make_d_map();
}
struct PartialSum {
  int sum[DISTING][DISTING];
  inline void init(int a[DISTING][DISTING]) {
    for (int i = 1; i <= sz_dx; ++i)
      for (int j = 1; j <= sz_dy; ++j) {
        sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + a[i][j];
      }
  }
  inline int query(int x1, int y1, int x2, int y2) {
    return sum[x2][y2] - sum[x1 - 1][y2] - sum[x2][y1 - 1] +
           sum[x1 - 1][y1 - 1];
  }
  inline int query_edge(int x1, int y1, int x2, int y2) {
    return query(x1, y1, x2, y2) - query(x1 + 1, y1 + 1, x2 - 1, y2 - 1);
  }
};
inline int full_edge(int x1, int y1, int x2, int y2) {
  return (x2 - x1 + 1) * 2 + (y2 - y1 + 1) * 2 - 4;
}
PartialSum cnt_sh;
int a_cnt_sh[DISTING][DISTING];
PartialSum cnt_r;
int a_cnt_r[DISTING][DISTING];
inline void find_rect_2(const Rect &rect_1) {
  memcpy(a_cnt_r, a_cnt_sh, sizeof(a_cnt_sh));
  for (int i = rect_1.LT.x; i <= rect_1.RB.x; ++i) {
    a_cnt_r[i][rect_1.LT.y] = 0;
    a_cnt_r[i][rect_1.RB.y] = 0;
  }
  for (int i = rect_1.LT.y; i <= rect_1.RB.y; ++i) {
    a_cnt_r[rect_1.LT.x][i] = 0;
    a_cnt_r[rect_1.RB.x][i] = 0;
  }
  cnt_r.init(a_cnt_r);
  int cnt_rem = cnt_r.query(1, 1, sz_dx, sz_dy);
  if (!cnt_rem) throw solution(rect_1, rect_1);
  Point RB_e;
  for (int i = sz_dx; i >= 1; --i)
    for (int j = sz_dy; j >= 1; --j) {
      if (cnt_r.query(1, 1, i, j) == cnt_rem) RB_e = Point(i, j);
    }
  for (int x1 = 1; x1 <= sz_dx; ++x1)
    for (int y1 = 1; y1 <= sz_dy; ++y1) {
      if (d_map[x1][y1] != '#') continue;
      if (cnt_r.query(x1, y1, sz_dx, sz_dy) != cnt_rem) continue;
      for (int x2 = max(x1 + 2, RB_e.x); x2 <= sz_dx; ++x2) {
        if (cnt_sh.query(x1, y1, x2, y1) != (x2 - x1 + 1)) break;
        for (int y2 = max(y1 + 2, RB_e.y); y2 <= sz_dy; ++y2) {
          if (cnt_sh.query(x1, y1, x1, y2) != (y2 - y1 + 1)) break;
          if (cnt_sh.query_edge(x1, y1, x2, y2) == full_edge(x1, y1, x2, y2) &&
              cnt_r.query_edge(x1, y1, x2, y2) == cnt_rem)
            throw solution(rect_1, Rect(Point(x1, y1), Point(x2, y2)));
        }
      }
    }
  throw no_solution("�Ҳ����Ϸ��ĵڶ������ε�ѡȡ����");
}
inline void find_rect_1() {
  for (int i = 1; i <= sz_dx; ++i)
    for (int j = 1; j <= sz_dy; ++j) {
      a_cnt_sh[i][j] = (d_map[i][j] == '#');
    }
  cnt_sh.init(a_cnt_sh);
  int cnt_tot_sh = cnt_sh.query(1, 1, sz_dx, sz_dy);
  if (cnt_tot_sh < 8 || cnt_tot_sh > 240)
    throw no_solution("��ɢ�����ͼ#������Ƿ�");
  Point LT;
  bool found_LT = false;
  for (int i = 1; i <= sz_dx && !found_LT; ++i)
    for (int j = 1; j <= sz_dy && !found_LT; ++j) {
      if (d_map[i][j] == '#') {
        found_LT = true;
        LT = Point(i, j);
      }
    }
  int x = LT.x, y = LT.y;
  for (int i = x + 2; i <= sz_dx; ++i) {
    if (cnt_sh.query(x, y, i, y) != (i - x + 1)) break;
    for (int j = y + 2; j <= sz_dy; ++j) {
      if (cnt_sh.query(x, y, x, j) != (j - y + 1)) break;
      if (cnt_sh.query_edge(x, y, i, j) == full_edge(x, y, i, j)) {
        try {
          find_rect_2(Rect(LT, Point(i, j)));
        } catch (no_solution) {
        }
      }
    }
  }
  throw no_solution(
      "���������׸�����ѡ������ʧ�ܡ"
      "�");
}
inline void output_rect(Rect a) {
  printf("%d %d %d %d\n", dx[a.LT.x], dy[a.LT.y], dx[a.RB.x], dy[a.RB.y]);
}
int main() {
  try {
    read_data();
    distinguish();
    find_rect_1();
  } catch (solution sol) {
    puts("YES");
    output_rect(sol.a);
    output_rect(sol.b);
    return 0;
  } catch (no_solution no_sol) {
    puts("NO");
    fprintf(stderr, "��Ϊ��%s�����޽⡣\n", no_sol.info);
    return 0;
  }
  return 1;
}
